git: 163cdf6a32b9 - main - ktls: Fix races that can lead to double initialization
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 08 Jul 2024 16:10:58 UTC
The branch main has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=163cdf6a32b9a0f84226a70101d143c10707336f commit 163cdf6a32b9a0f84226a70101d143c10707336f Author: Mark Johnston <markj@FreeBSD.org> AuthorDate: 2024-07-08 15:49:29 +0000 Commit: Mark Johnston <markj@FreeBSD.org> CommitDate: 2024-07-08 16:10:48 +0000 ktls: Fix races that can lead to double initialization ktls_enable_rx() and ktls_enable_tx() have checks to return EALREADY if the socket already has KTLS enabled. However, these are done without any locks held and nothing blocks concurrent attempts to set the socket option. I believe the worst outcome of the race is leaked memory. Fix the problem by rechecking under the sockbuf lock. While here, unify the locking protocol for sb_tls_info: require both the sockbuf and socket I/O locks in order to enable KTLS. This means that either lock is sufficient for checking whether KTLS is enabled in a given sockbuf, which simplifies some refactoring further down the road. Note that the SOLISTENING() check can go away because SOCK_IO_RECV_LOCK() atomically locks the socket buffer and checks whether the socket is a listening socket. This changes the returned errno value, so update a test which checks it. Reviewed by: gallatin MFC after: 2 weeks Sponsored by: Klara, Inc. Sponsored by: Stormshield Differential Revision: https://reviews.freebsd.org/D45674 --- sys/kern/uipc_ktls.c | 23 +++++++++++++++++++++-- sys/sys/sockbuf.h | 3 ++- tests/sys/kern/ktls_test.c | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/sys/kern/uipc_ktls.c b/sys/kern/uipc_ktls.c index fd1bc7bf8bfe..5ea99966bf13 100644 --- a/sys/kern/uipc_ktls.c +++ b/sys/kern/uipc_ktls.c @@ -1320,12 +1320,23 @@ ktls_enable_rx(struct socket *so, struct tls_enable *en) return (error); } + /* + * Serialize with soreceive_generic() and make sure that we're not + * operating on a listening socket. + */ + error = SOCK_IO_RECV_LOCK(so, SBL_WAIT); + if (error) { + ktls_free(tls); + return (error); + } + /* Mark the socket as using TLS offload. */ SOCK_RECVBUF_LOCK(so); - if (SOLISTENING(so)) { + if (__predict_false(so->so_rcv.sb_tls_info != NULL)) { SOCK_RECVBUF_UNLOCK(so); + SOCK_IO_RECV_UNLOCK(so); ktls_free(tls); - return (EINVAL); + return (EALREADY); } so->so_rcv.sb_tls_seqno = be64dec(en->rec_seq); so->so_rcv.sb_tls_info = tls; @@ -1335,6 +1346,7 @@ ktls_enable_rx(struct socket *so, struct tls_enable *en) sb_mark_notready(&so->so_rcv); ktls_check_rx(&so->so_rcv); SOCK_RECVBUF_UNLOCK(so); + SOCK_IO_RECV_UNLOCK(so); /* Prefer TOE -> ifnet TLS -> software TLS. */ #ifdef TCP_OFFLOAD @@ -1420,6 +1432,13 @@ ktls_enable_tx(struct socket *so, struct tls_enable *en) inp = so->so_pcb; INP_WLOCK(inp); SOCK_SENDBUF_LOCK(so); + if (__predict_false(so->so_snd.sb_tls_info != NULL)) { + SOCK_SENDBUF_UNLOCK(so); + INP_WUNLOCK(inp); + SOCK_IO_SEND_UNLOCK(so); + ktls_free(tls); + return (EALREADY); + } so->so_snd.sb_tls_seqno = be64dec(en->rec_seq); so->so_snd.sb_tls_info = tls; if (tls->mode != TCP_TLS_MODE_SW) { diff --git a/sys/sys/sockbuf.h b/sys/sys/sockbuf.h index c6093883be4a..a2e327bbb5df 100644 --- a/sys/sys/sockbuf.h +++ b/sys/sys/sockbuf.h @@ -128,7 +128,8 @@ struct sockbuf { struct mbuf *sb_mtls; /* TLS mbuf chain */ struct mbuf *sb_mtlstail; /* last mbuf in TLS chain */ uint64_t sb_tls_seqno; /* TLS seqno */ - struct ktls_session *sb_tls_info; /* TLS state */ + /* TLS state, locked by sockbuf and sock I/O mutexes. */ + struct ktls_session *sb_tls_info; }; /* * PF_UNIX/SOCK_DGRAM diff --git a/tests/sys/kern/ktls_test.c b/tests/sys/kern/ktls_test.c index f57ae74112a2..72497196b945 100644 --- a/tests/sys/kern/ktls_test.c +++ b/tests/sys/kern/ktls_test.c @@ -2812,7 +2812,7 @@ ATF_TC_BODY(ktls_listening_socket, tc) TLS_MINOR_VER_THREE, (uint64_t)random(), &en); ATF_REQUIRE_ERRNO(ENOTCONN, setsockopt(s, IPPROTO_TCP, TCP_TXTLS_ENABLE, &en, sizeof(en)) != 0); - ATF_REQUIRE_ERRNO(EINVAL, + ATF_REQUIRE_ERRNO(ENOTCONN, setsockopt(s, IPPROTO_TCP, TCP_RXTLS_ENABLE, &en, sizeof(en)) != 0); ATF_REQUIRE(close(s) == 0); }