From 17712295cfaa3d50354380ce00fdbe2e763b6cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 30 Oct 2026 11:07:52 +0100 Subject: [PATCH 1/3] Use rfd/wfd as the default IO context for non-DTLS SSLs With WOLFSSL_DTLS compiled in, InitSSL pointed IOCB_ReadCtx and IOCB_WriteCtx at ssl->buffers.dtlsCtx for every WOLFSSL, TLS ones included. EmbedReceive and EmbedSend read the socket as *(int*)ctx, and the first member of WOLFSSL_DTLS_CTX is the zeroed peer address, so a TLS object that never had an fd set did its I/O on fd 0 instead of failing on -1. When stdin is an idle socket, an accept or connect without an fd blocks forever; test_tls13_apis hits this through wolfSSL_read_early_data on a server with no fd. Choose the default IO context in one place for builds with and without DTLS. NetX and GNRC keep their port context, and the socket default is &ssl->rfd/&ssl->wfd, or the DTLS context when the method is DTLS. GNRC objects in builds with DTLS now get the port context their I/O callbacks expect instead of the DTLS one. The new test checks the default contexts of a TLS and a DTLS object, and puts a socket on fd 0 to check that a client with no fd, and one with only a write fd, fail with SOCKET_ERROR_E without writing to or reading from fd 0. --- src/internal.c | 17 +++++------ tests/api/test_tls.c | 70 ++++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 4 ++- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/internal.c b/src/internal.c index 60f13ef08f8..449c232dbfa 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9419,14 +9419,8 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) if (wc_InitRwLock(&ssl->buffers.dtlsCtx.peerLock) != 0) return BAD_MUTEX_E; #endif -#ifdef HAVE_NETX - ssl->IOCB_ReadCtx = &ssl->nxCtx; /* default NetX IO ctx, same for read */ - ssl->IOCB_WriteCtx = &ssl->nxCtx; /* and write */ -#else - ssl->IOCB_ReadCtx = &ssl->buffers.dtlsCtx; /* prevent invalid pointer access if not */ - ssl->IOCB_WriteCtx = &ssl->buffers.dtlsCtx; /* correctly set */ -#endif -#else +#endif /* WOLFSSL_DTLS */ + #ifdef HAVE_NETX ssl->IOCB_ReadCtx = &ssl->nxCtx; /* default NetX IO ctx, same for read */ ssl->IOCB_WriteCtx = &ssl->nxCtx; /* and write */ @@ -9436,8 +9430,13 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) #else ssl->IOCB_ReadCtx = &ssl->rfd; /* prevent invalid pointer access if not */ ssl->IOCB_WriteCtx = &ssl->wfd; /* correctly set */ +#ifdef WOLFSSL_DTLS + if (ctx->method->version.major == DTLS_MAJOR) { + ssl->IOCB_ReadCtx = &ssl->buffers.dtlsCtx; + ssl->IOCB_WriteCtx = &ssl->buffers.dtlsCtx; + } +#endif #endif -#endif /* WOLFSSL_DTLS */ #ifndef WOLFSSL_AEAD_ONLY diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index b8b8b29b46c..e4b055f7608 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -9010,3 +9010,73 @@ int test_tls_hmac_inner_cid_decision_coverage(void) #endif /* WOLFSSL_DTLS */ return EXPECT_RESULT(); } /* END test_tls_hmac_inner_cid_decision_coverage */ + +int test_tls_unset_fd_no_stdin_io(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(USE_WOLFSSL_IO) && (defined(__unix__) || defined(__APPLE__)) && \ + !defined(WOLFSSL_LEANPSK) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + int sv[2] = { -1, -1 }; + int wr[2] = { -1, -1 }; + int savedStdin = -1; + byte buf[1] = { 0 }; + + /* Put a socket on fd 0 so that any I/O on it is visible. */ + ExpectIntEQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); + ExpectIntEQ(socketpair(AF_UNIX, SOCK_STREAM, 0, wr), 0); + ExpectIntEQ(fcntl(sv[0], F_SETFL, O_NONBLOCK), 0); + ExpectIntEQ(fcntl(sv[1], F_SETFL, O_NONBLOCK), 0); + ExpectIntEQ(fcntl(wr[0], F_SETFL, O_NONBLOCK), 0); + ExpectIntGE(savedStdin = dup(0), 0); + ExpectIntEQ(dup2(sv[0], 0), 0); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectPtrEq(wolfSSL_GetIOReadCtx(ssl), &ssl->rfd); + ExpectPtrEq(wolfSSL_GetIOWriteCtx(ssl), &ssl->wfd); + ExpectIntEQ(wolfSSL_connect(ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(SOCKET_ERROR_E)); + ExpectIntEQ(recv(sv[1], buf, sizeof(buf), 0), -1); + wolfSSL_free(ssl); + ssl = NULL; + + ExpectIntEQ(send(sv[1], buf, sizeof(buf), 0), 1); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_write_fd(ssl, wr[0]), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(SOCKET_ERROR_E)); + ExpectIntEQ(recv(sv[0], buf, sizeof(buf), MSG_PEEK), 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + +#ifdef WOLFSSL_DTLS + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectPtrEq(wolfSSL_GetIOReadCtx(ssl), &ssl->buffers.dtlsCtx); + ExpectPtrEq(wolfSSL_GetIOWriteCtx(ssl), &ssl->buffers.dtlsCtx); +#endif + + if (savedStdin >= 0) { + (void)dup2(savedStdin, 0); + close(savedStdin); + } + if (sv[0] >= 0) + close(sv[0]); + if (sv[1] >= 0) + close(sv[1]); + if (wr[0] >= 0) + close(wr[0]); + if (wr[1] >= 0) + close(wr[1]); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 0c5a30b1ef4..312c3d64db4 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -132,6 +132,7 @@ int test_wolfSSL_get_shared_ciphers(void); int test_tls12_aesgcm_record_nonce_unique(void); int test_tls_param_flags_crl_check(void); int test_tls_hmac_inner_cid_decision_coverage(void); +int test_tls_unset_fd_no_stdin_io(void); #define TEST_TLS_DECLS \ TEST_DECL_GROUP("tls", test_utils_memio_move_message), \ @@ -250,6 +251,7 @@ int test_tls_hmac_inner_cid_decision_coverage(void); TEST_DECL_GROUP("tls", test_chain_verify_cb_stapling), \ TEST_DECL_GROUP("tls", test_internal_SetSSL_CTX_DecisionCoverage), \ TEST_DECL_GROUP("tls", test_internal_CheckVersion_DecisionCoverage), \ - TEST_DECL_GROUP("tls", test_tls_hmac_inner_cid_decision_coverage) + TEST_DECL_GROUP("tls", test_tls_hmac_inner_cid_decision_coverage), \ + TEST_DECL_GROUP("tls", test_tls_unset_fd_no_stdin_io) #endif /* TESTS_API_TEST_TLS_H */ From bbc6c364721ca3fd4dd5626a6994ec5362827e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 30 Oct 2026 12:03:41 +0100 Subject: [PATCH 2/3] Route SSL_set_rfd and SSL_set_wfd through the fd setters The OpenSSL compat wolfSSL_set_wfd only stored the fd in ssl->wfd and pointed IOCB_WriteCtx at it, also for DTLS objects. EmbedSendTo casts the write context to WOLFSSL_DTLS_CTX, so it read the socket, the peer address and the sendto callback from the WOLFSSL fields that follow wfd. wolfSSL_set_rfd handled DTLS but did not reset dtlsCtx.connected, and neither checked ssl for NULL. Make both wrappers around wolfSSL_set_read_fd and wolfSSL_set_write_fd, which select the DTLS context and fill it in. --- src/ssl.c | 23 ++--------------------- tests/api/test_dtls.c | 38 ++++++++++++++++++++++++++++++++++++++ tests/api/test_dtls.h | 4 +++- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index 3773badb1d9..d83f9f64b6d 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -4734,33 +4734,14 @@ int wolfSSL_set_compression(WOLFSSL* ssl) int wolfSSL_set_rfd(WOLFSSL* ssl, int rfd) { WOLFSSL_ENTER("wolfSSL_set_rfd"); - ssl->rfd = rfd; /* not used directly to allow IO callbacks */ - - ssl->IOCB_ReadCtx = &ssl->rfd; - - #ifdef WOLFSSL_DTLS - if (ssl->options.dtls) { - ssl->IOCB_ReadCtx = &ssl->buffers.dtlsCtx; - ssl->buffers.dtlsCtx.rfd = rfd; - #ifdef USE_WOLFSSL_IO - ssl->buffers.dtlsCtx.rfdIsDGram = - (byte)(wolfIO_SockIsDGram(rfd) != 0); - #endif - } - #endif - - return WOLFSSL_SUCCESS; + return wolfSSL_set_read_fd(ssl, rfd); } int wolfSSL_set_wfd(WOLFSSL* ssl, int wfd) { WOLFSSL_ENTER("wolfSSL_set_wfd"); - ssl->wfd = wfd; /* not used directly to allow IO callbacks */ - - ssl->IOCB_WriteCtx = &ssl->wfd; - - return WOLFSSL_SUCCESS; + return wolfSSL_set_write_fd(ssl, wfd); } #endif /* OPENSSL_EXTRA */ diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 2fb4f10ef28..ba52684e96b 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16431,3 +16431,41 @@ int test_dtls_max_fragment_write(void) #endif return EXPECT_RESULT(); } + +int test_dtls_set_rfd_wfd(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_DTLS) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(USE_WOLFSSL_IO) && \ + (defined(__unix__) || defined(__APPLE__)) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + int sv[2] = { -1, -1 }; + byte buf[1]; + + ExpectIntEQ(socketpair(AF_UNIX, SOCK_DGRAM, 0, sv), 0); + ExpectIntEQ(fcntl(sv[0], F_SETFL, O_NONBLOCK), 0); + ExpectIntEQ(fcntl(sv[1], F_SETFL, O_NONBLOCK), 0); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_rfd(ssl, sv[0]), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_wfd(ssl, sv[0]), WOLFSSL_SUCCESS); + ExpectPtrEq(wolfSSL_GetIOReadCtx(ssl), &ssl->buffers.dtlsCtx); + ExpectPtrEq(wolfSSL_GetIOWriteCtx(ssl), &ssl->buffers.dtlsCtx); + ExpectIntEQ(ssl->buffers.dtlsCtx.rfd, sv[0]); + ExpectIntEQ(ssl->buffers.dtlsCtx.wfd, sv[0]); + wolfSSL_dtls_set_using_nonblock(ssl, 1); + ExpectIntEQ(wolfSSL_connect(ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(recv(sv[1], buf, sizeof(buf), MSG_PEEK), 1); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + if (sv[0] >= 0) + close(sv[0]); + if (sv[1] >= 0) + close(sv[1]); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 2a96a9bb731..7fc945fbaa0 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -179,6 +179,7 @@ int test_dtls13_hrr_cookie_state_guard(void); int test_dtls12_cookie_secret_generate_fail(void); int test_dtls13_hrr_cookie_secret_generate_fail(void); int test_dtls_max_fragment_write(void); +int test_dtls_set_rfd_wfd(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_import_dh_key_sz(void); int test_wolfSSL_dtls_export_peers(void); @@ -400,5 +401,6 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_disabled), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_generate_fail), \ - TEST_DECL_GROUP("dtls", test_dtls_max_fragment_write) + TEST_DECL_GROUP("dtls", test_dtls_max_fragment_write), \ + TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd) #endif /* TESTS_API_DTLS_H */ From 7c8123cbe967c1407f923381d35d9d654976fd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 6 Nov 2026 08:43:22 +0100 Subject: [PATCH 3/3] Address review feedback Flatten the default IO context selection in InitSSL into one #elif chain instead of nesting the DTLS check inside the #else branch. --- src/internal.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/internal.c b/src/internal.c index 449c232dbfa..9b41b8c367b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9427,15 +9427,18 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) #elif defined (WOLFSSL_GNRC) ssl->IOCB_ReadCtx = ssl->gnrcCtx; ssl->IOCB_WriteCtx = ssl->gnrcCtx; -#else - ssl->IOCB_ReadCtx = &ssl->rfd; /* prevent invalid pointer access if not */ - ssl->IOCB_WriteCtx = &ssl->wfd; /* correctly set */ -#ifdef WOLFSSL_DTLS +#elif defined(WOLFSSL_DTLS) if (ctx->method->version.major == DTLS_MAJOR) { ssl->IOCB_ReadCtx = &ssl->buffers.dtlsCtx; ssl->IOCB_WriteCtx = &ssl->buffers.dtlsCtx; } -#endif + else { + ssl->IOCB_ReadCtx = &ssl->rfd; + ssl->IOCB_WriteCtx = &ssl->wfd; + } +#else + ssl->IOCB_ReadCtx = &ssl->rfd; /* prevent invalid pointer access if not */ + ssl->IOCB_WriteCtx = &ssl->wfd; /* correctly set */ #endif