From 6b9736472cc9df915d9f727047b2f262b98b5b0f Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Sun, 10 Jan 2021 13:32:27 +0100 Subject: [PATCH] OpenSSL file transfer failed --- CHANGES | 10 ++ test.sh | 298 +++++++++++++++++++++++++++++++++++++++++++++++++- xio-openssl.c | 16 ++- xio-openssl.h | 2 +- xioshutdown.c | 5 +- 5 files changed, 325 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index af4d272..59f4eff 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,16 @@ Corrections: Thanks to Wang Mingyu and others for sending a patch or reporting this issue. + Under certain conditions OpenSSL stream connections, in particular bulk + data transfer in unidirectional mode, failed during transfer or near + its with Connection reset by peer on receiver side. + This happened with Socat versions 1.7.3.3 to 1.7.4.0. Reasons were + lazy SSL shutdown handling on the sender side in combination with + SSL_MODE_AUTO_RETRY turned off. + Fix: After SSH_shutdown but before socket shutdown call SSL_read() + Test: OPENSSL_STREAM_TO_SERVER + Fixes Red Hat issue 1870279. + ####################### V 1.7.4.0: Security: diff --git a/test.sh b/test.sh index b9e659d..23f1ee8 100755 --- a/test.sh +++ b/test.sh @@ -45,6 +45,7 @@ case "X$val_t" in esac MICROS=${S}${uS} MICROS=${MICROS##0000}; MICROS=${MICROS##00}; MICROS=${MICROS##0} +#echo MICROS=$MICROS >&2 # _MICROS=$((MICROS+999999)); SECONDs="${_MICROS%??????}" [ -z "$SECONDs" ] && SECONDs=0 @@ -4437,11 +4438,12 @@ TESTADDR=$(eval echo $TESTTMPL) PEERADDR=$(eval echo $PEERTMPL) WAITCMD=$(eval echo $WAITTMPL) TESTKEYW=${TESTADDR%%:*} +feat=$(tolower $FEAT) # does our address implementation support halfclose? NAME=${NAMEKEYW}_HALFCLOSE case "$TESTS" in -*%$N%*|*%functions%*|*%$FEAT%*|*%socket%*|*%halfclose%*|*%$NAME%*) +*%$N%*|*%functions%*|*%$feat%*|*%socket%*|*%halfclose%*|*%$NAME%*) TEST="$NAME: $TESTKEYW half close" # have a "peer" socat "peer" that executes "$OD_C" and see if EOF on the # connecting socat brings the result of od @@ -5241,7 +5243,7 @@ TEST="$NAME: for bug with address options on both stdin/out in unidirectional mo if ! eval $NUMCOND; then :; else tf="$td/test$N.stdout" te="$td/test$N.stderr" -ff="$td/file$N" +ff="$td/test$N.file" printf "test $F_n $TEST... " $N >"$ff" #$TRACE $SOCAT $opts -u /dev/null -,setlk <"$ff" 2>"$te" @@ -14672,6 +14674,297 @@ PORT=$((PORT+1)) N=$((N+1)) +# File transfer with OpenSSL stream connection was incomplete +# Test file transfer from client to server +NAME=OPENSSL_STREAM_TO_SERVER +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%openssl%*|*%tcp%*|*%socket%*|*%$NAME%*) +TEST="$NAME: OpenSSL stream from client to server" +# Start a unidirectional OpenSSL server and stream receiver +# Start a unidirectional OpenSSL client that connects to the server and sends +# data +# Test succeeded when the data received and stored by server is the same as +# sent by the client +if ! eval $NUMCOND; then :; +elif ! a=$(testfeats ip4 tcp openssl); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! a=$(testaddrs openssl-listen openssl-connect); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +gentestcert testsrv +ti="$td/test$N.datain" +to="$td/test$N.dataout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts -u OPENSSL-LISTEN:$PORT,$REUSEADDR,cert=testsrv.pem,verify=0 CREAT:$to" +CMD1="$TRACE $SOCAT $opts -u OPEN:$ti OPENSSL-CONNECT:$LOCALHOST:$PORT,cafile=testsrv.crt" +printf "test $F_n $TEST... " $N +i=0; while [ $i -lt 100000 ]; do printf "%9u %9u %9u %9u %9u %9u %9u %9u %9u %9u\n" $i $i $i $i $i $i $i $i $i $i; let i+=100; done >$ti +$CMD0 >/dev/null 2>"${te}0" & +pid0=$! +waittcp4port $PORT 1 +$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +usleep $MICROS +kill $pid0 2>/dev/null; wait +if [ $rc1 -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +elif diff $ti $to >$tdiff; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + echo "diff:" >&2 + head -n 2 $tdiff >&2 + echo ... >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) + +# File transfer with OpenSSL stream connection was incomplete +# Test file transfer from server to client +NAME=OPENSSL_STREAM_TO_CLIENT +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%openssl%*|*%tcp%*|*%socket%*|*%$NAME%*) +TEST="$NAME: OpenSSL stream from server to client" +# Start a unidirectional OpenSSL server and stream sender +# Start a unidirectional OpenSSL client that connects to the server and receives +# data +# Test succeeded when the data received and stored by client is the same as +# sent by the server +if ! eval $NUMCOND; then :; +elif ! a=$(testfeats ip4 tcp openssl); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! a=$(testaddrs openssl-listen openssl-connect); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +gentestcert testsrv +ti="$td/test$N.datain" +to="$td/test$N.dataout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts -U OPENSSL-LISTEN:$PORT,$REUSEADDR,cert=testsrv.pem,verify=0 OPEN:$ti" +CMD1="$TRACE $SOCAT $opts -u OPENSSL-CONNECT:$LOCALHOST:$PORT,cafile=testsrv.crt CREAT:$to" +printf "test $F_n $TEST... " $N +i=0; while [ $i -lt 100000 ]; do printf "%9u %9u %9u %9u %9u %9u %9u %9u %9u %9u\n" $i $i $i $i $i $i $i $i $i $i; let i+=100; done >$ti +$CMD0 >/dev/null 2>"${te}0" & +pid0=$! +waittcp4port $PORT 1 +$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +usleep $MICROS +kill $pid0 2>/dev/null; wait +if [ $rc1 -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +elif diff $ti $to >$tdiff; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + echo "diff:" >&2 + head -n 2 $tdiff >&2 + echo ... >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) + +# Test file transfer from client to server using DTLS +NAME=OPENSSL_DTLS_TO_SERVER +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%openssl%*|*%dtls%*|*%udp%*|*%socket%*|*%$NAME%*) +TEST="$NAME: OpenSSL DTLS transfer from client to server" +# Start a unidirectional OpenSSL DTLS server/receiver +# Start a unidirectional OpenSSL DTLS client that connects to the server and +# sends data +# Test succeeded when the data received and stored by server is the same as +# sent by the client +if ! eval $NUMCOND; then :; +elif ! a=$(testfeats ip4 udp openssl); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! a=$(testaddrs openssl-dtls-listen openssl-dtls-connect); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif [[ $(openssl version |awk '{print($2);}') =~ 0.9.8[a-c] ]]; then + $PRINTF "test $F_n $TEST... ${YELLOW}openssl s_client might hang${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +gentestcert testsrv +ti="$td/test$N.datain" +to="$td/test$N.dataout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts -u OPENSSL-DTLS-LISTEN:$PORT,cert=testsrv.pem,verify=0 CREAT:$to" +CMD1="$TRACE $SOCAT $opts -u OPEN:$ti OPENSSL-DTLS-CONNECT:$LOCALHOST:$PORT,cafile=testsrv.crt" +printf "test $F_n $TEST... " $N +i=0; while [ $i -lt 100000 ]; do printf "%9u %9u %9u %9u %9u %9u %9u %9u %9u %9u\n" $i $i $i $i $i $i $i $i $i $i; let i+=100; done >$ti +$CMD0 >/dev/null 2>"${te}0" & +pid0=$! +waitudp4port $PORT 1 +$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +usleep $MICROS +kill $pid0 2>/dev/null; wait +if [ $rc1 -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +elif diff $ti $to >$tdiff; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + echo "diff:" >&2 + head -n 2 $tdiff >&2 + echo ... >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) + +# Test file transfer from server to client using DTLS +NAME=OPENSSL_DTLS_TO_CLIENT +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%openssl%*|*%dtls%*|*%udp%*|*%socket%*|*%$NAME%*) +TEST="$NAME: OpenSSL DTLS transfer from server to client" +# Start a unidirectional OpenSSL DTLS server/sender +# Start a unidirectional OpenSSL DTLS client that connects to the server and +# receives data +# Test succeeded when the data received and stored by client is the same as +# sent by the server +if ! eval $NUMCOND; then :; +elif ! a=$(testfeats ip4 udp openssl); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! a=$(testaddrs openssl-dtls-listen openssl-dtls-connect); then + $PRINTF "test $F_n $TEST... ${YELLOW}$a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif [[ $(openssl version |awk '{print($2);}') =~ 0.9.8[a-c] ]]; then + $PRINTF "test $F_n $TEST... ${YELLOW}openssl s_client might hang${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +gentestcert testsrv +ti="$td/test$N.datain" +to="$td/test$N.dataout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts -U OPENSSL-DTLS-LISTEN:$PORT,cert=testsrv.pem,verify=0 OPEN:$ti" +CMD1="$TRACE $SOCAT $opts -u OPENSSL-DTLS-CONNECT:$LOCALHOST:$PORT,cafile=testsrv.crt CREAT:$to" +printf "test $F_n $TEST... " $N +i=0; while [ $i -lt 100000 ]; do printf "%9u %9u %9u %9u %9u %9u %9u %9u %9u %9u\n" $i $i $i $i $i $i $i $i $i $i; let i+=100; done >$ti +$CMD0 >/dev/null 2>"${te}0" & +pid0=$! +waitudp4port $PORT 1 +$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +usleep $MICROS +kill $pid0 2>/dev/null; wait +if [ $rc1 -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +elif diff $ti $to >$tdiff; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + echo "diff:" >&2 + head -n 2 $tdiff >&2 + echo ... >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) + + ################################################################################## #================================================================================= # here come tests that might affect your systems integrity. Put normal tests @@ -14768,6 +15061,7 @@ wait exit +#============================================================================== # test template # give a description of what is tested (a bugfix, a new feature...) diff --git a/xio-openssl.c b/xio-openssl.c index aec1bd1..bc22b8c 100644 --- a/xio-openssl.c +++ b/xio-openssl.c @@ -1303,7 +1303,7 @@ cont_out: mode = SSL_CTX_get_mode(ctx); if (mode & SSL_MODE_AUTO_RETRY) { Info("SSL_CTX mode has SSL_MODE_AUTO_RETRY set. Correcting.."); - Debug1("SSL_CTX_clean_mode(%p, SSL_MODE_AUTO_RETRY)", ctx); + Debug1("SSL_CTX_clear_mode(%p, SSL_MODE_AUTO_RETRY)", ctx); SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY); } } @@ -2023,5 +2023,19 @@ ssize_t xiowrite_openssl(struct single *pipe, const void *buff, size_t bufsiz) { return ret; } +int xioshutdown_openssl(struct single *sfd, int how) +{ + int rc; + + if ((rc = sycSSL_shutdown(sfd->para.openssl.ssl)) < 0) { + Warn1("xioshutdown_openssl(): SSL_shutdown() -> %d", rc); + } + if (sfd->tag == XIO_TAG_WRONLY) { + char buff[1]; + /* give peer time to read all data before closing socket */ + xioread_openssl(sfd, buff, 1); + } + return 0; +} #endif /* WITH_OPENSSL */ diff --git a/xio-openssl.h b/xio-openssl.h index e9edcfc..6ec3d3a 100644 --- a/xio-openssl.h +++ b/xio-openssl.h @@ -51,7 +51,7 @@ extern int const char *opt_commonname, SSL_CTX *ctx, int level); extern int xioclose_openssl(xiofile_t *xfd); -extern int xioshutdown_openssl(xiofile_t *xfd, int how); +extern int xioshutdown_openssl(struct single *sfd, int how); extern ssize_t xioread_openssl(struct single *file, void *buff, size_t bufsiz); extern ssize_t xiopending_openssl(struct single *pipe); extern ssize_t xiowrite_openssl(struct single *file, const void *buff, size_t bufsiz); diff --git a/xioshutdown.c b/xioshutdown.c index 6470782..c9fef8d 100644 --- a/xioshutdown.c +++ b/xioshutdown.c @@ -8,6 +8,8 @@ #include "xiosysincludes.h" #include "xioopen.h" +#include "xio-openssl.h" + static pid_t socat_kill_pid; /* here we pass the pid to be killed in sighandler */ static void signal_kill_pid(int dummy) { @@ -68,8 +70,7 @@ int xioshutdown(xiofile_t *sock, int how) { ; #if WITH_OPENSSL } else if ((sock->stream.dtype & XIODATA_MASK) == XIODATA_OPENSSL) { - sycSSL_shutdown (sock->stream.para.openssl.ssl); - /*! what about half/full close? */ + xioshutdown_openssl(&sock->stream, how); #endif /* WITH_OPENSSL */ } else if ((sock->stream.dtype & XIODATA_MASK) == XIODATA_PIPE) {