diff --git a/src/internal.c b/src/internal.c index f142c0db9..6ebfa577f 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5910,7 +5910,14 @@ static int GetInputData(WOLFSSH* ssh, word32 size) return WS_FATAL_ERROR; } - if (in >= 0) { + /* A recv-style 0 is the peer closing the connection. */ + if (in == 0) { + ssh->isClosed = 1; + ssh->error = WS_SOCKET_ERROR_E; + return WS_FATAL_ERROR; + } + + if (in > 0) { ssh->inputBuffer.length += in; size -= in; } diff --git a/src/io.c b/src/io.c index 563cc8c25..83216f306 100644 --- a/src/io.c +++ b/src/io.c @@ -450,6 +450,10 @@ int wsEmbedSend(WOLFSSH* ssh, void* data, word32 sz, void* ctx) WLOG(WS_LOG_DEBUG," Socket EPIPE"); return WS_CBIO_ERR_CONN_CLOSE; } + else if (err == SOCKET_ECONNABORTED) { + WLOG(WS_LOG_DEBUG," Connection aborted"); + return WS_CBIO_ERR_CONN_CLOSE; + } else { WLOG(WS_LOG_DEBUG," General error %d", err); return WS_CBIO_ERR_GENERAL; diff --git a/tests/unit.c b/tests/unit.c index ffc3517d6..7e50cead4 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -2713,6 +2713,142 @@ static int test_DoReceive_CbcRejectDiscards(void) #endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_AES_CBC && HMAC-SHA2-256 */ +#ifdef WOLFSSH_TEST_INTERNAL +/* Gives up after this many calls, so an unfixed reader that keeps + * retrying a 0 return still terminates. */ +#define RECV_ZERO_CALL_CAP 100 + +static int RecvZeroCalls; + +static int RecvAlwaysZero(WOLFSSH* ssh, void* data, word32 sz, void* ctx) +{ + WOLFSSH_UNUSED(ssh); + WOLFSSH_UNUSED(data); + WOLFSSH_UNUSED(sz); + WOLFSSH_UNUSED(ctx); + + if (++RecvZeroCalls > RECV_ZERO_CALL_CAP) + return WS_CBIO_ERR_GENERAL; + return 0; +} + +/* Verify DoReceive treats a 0 from the recv callback as the peer closing + * the connection: it fails after one call and marks the session closed, + * instead of calling the callback again. */ +static int test_DoReceive_RecvZeroIsClose(void) +{ + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + int ret; + int result = 0; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) + return -794; + wolfSSH_SetIORecv(ctx, RecvAlwaysZero); + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { + wolfSSH_CTX_free(ctx); + return -795; + } + + RecvZeroCalls = 0; + ret = wolfSSH_TestDoReceive(ssh); + if (ret != WS_FATAL_ERROR || ssh->error != WS_SOCKET_ERROR_E) { + result = -796; + goto done; + } + if (RecvZeroCalls != 1) { + result = -797; + goto done; + } + if (!ssh->isClosed) { + result = -798; + goto done; + } + +done: + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + return result; +} +#endif /* WOLFSSH_TEST_INTERNAL */ + + +#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_USER_IO) && \ + !defined(WOLFSSH_TEST_BLOCK) && !defined(WOLFSSH_LWIP) && \ + (defined(__linux__) || defined(__APPLE__)) +#include +#include + +/* No socket can be made to fail a send() with ECONNABORTED on demand, so + * this test replaces send() for the whole program. The library is linked in + * statically, so wsEmbedSend() binds to this definition. A send() on the + * marked descriptor fails with the chosen errno; any other passes through + * to sendto(), which is send() on a connected socket. */ +#define SEND_ERRNO_FD 0x5e4d +static int sendErrnoInject = 0; +static int sendErrnoCalls = 0; + +ssize_t send(int sd, const void* buf, size_t len, int flags) +{ + if (sd == SEND_ERRNO_FD && sendErrnoInject != 0) { + sendErrnoCalls++; + errno = sendErrnoInject; + return -1; + } + return sendto(sd, buf, len, flags, NULL, 0); +} + +/* Verify wsEmbedSend maps ECONNABORTED to a closed connection, and that + * the packet send path then marks the session closed. */ +static int test_EmbedSend_ConnAbortedIsClose(void) +{ + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + byte data[1] = { 0 }; + int ret; + int result = 0; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) + return -799; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { + wolfSSH_CTX_free(ctx); + return -800; + } + wolfSSH_set_fd(ssh, SEND_ERRNO_FD); + sendErrnoInject = ECONNABORTED; + sendErrnoCalls = 0; + + ret = wsEmbedSend(ssh, data, (word32)sizeof(data), ssh->ioWriteCtx); + if (sendErrnoCalls != 1 || ret != WS_CBIO_ERR_CONN_CLOSE) { + result = -801; + goto done; + } + + /* Not keyed yet, so strict KEX would refuse the IGNORE outright. */ + ssh->sendStrictKex = 0; + ret = wolfSSH_SendIgnore(ssh, NULL, 0); + if (ret != WS_SOCKET_ERROR_E || ssh->error != WS_SOCKET_ERROR_E) { + result = -802; + goto done; + } + if (!ssh->isClosed) { + result = -803; + goto done; + } + +done: + sendErrnoInject = 0; + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + return result; +} +#endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_USER_IO && (linux || apple) */ + + #if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_GCM) /* Verify DoReceive rejects an AES-GCM record whose length field is not a * multiple of the cipher block size. RFC 5647 aligns the body alone, so a @@ -25979,6 +26115,20 @@ int wolfSSH_UnitTest(int argc, char** argv) printf("DoReceiveRejectsMisalignedPacket: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; + + unitResult = test_DoReceive_RecvZeroIsClose(); + printf("DoReceiveRecvZeroIsClose: %s\n", + (unitResult == 0 ? "SUCCESS" : "FAILED")); + testResult = testResult || unitResult; +#endif + +#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_USER_IO) && \ + !defined(WOLFSSH_TEST_BLOCK) && !defined(WOLFSSH_LWIP) && \ + (defined(__linux__) || defined(__APPLE__)) + unitResult = test_EmbedSend_ConnAbortedIsClose(); + printf("EmbedSendConnAbortedIsClose: %s\n", + (unitResult == 0 ? "SUCCESS" : "FAILED")); + testResult = testResult || unitResult; #endif #if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_CTR)