* [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt
@ 2026-08-07 0:44 Chuck Lever
2026-08-07 0:44 ` [PATCH net 1/2] net/tls: Fail tls_sw_splice_read() " Chuck Lever
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Chuck Lever @ 2026-08-07 0:44 UTC (permalink / raw)
To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Shuah Khan
Cc: netdev, linux-kernel, linux-kselftest, Chuck Lever
tls_sw_recvmsg() and tls_sw_read_sock() both read ctx->async_wait.err
once they hold the reader lock, so a record that failed
authentication fails the call. tls_sw_splice_read() has no such
check. sk_err does not stand in for one. The first reader to reach
sock_error() clears sk_err, while async_wait.err persists. A splice
therefore keeps delivering records on a connection the other two
readers have already refused.
Both patches come from a receive-path series for zero-length data
records. Jakub asked for them separately, since the rest of that
series is still under discussion.
Link to the original series:
https://patch.msgid.link/20260726-tls-follow-on-v1-0-99bf4cc1c729@kernel.org
---
Chuck Lever (2):
net/tls: Fail tls_sw_splice_read() after a failed async decrypt
selftests: tls: cover splice after a failed decrypt
net/tls/tls_sw.c | 5 +++
tools/testing/selftests/net/tls.c | 75 +++++++++++++++++++++++++++++++++------
2 files changed, 70 insertions(+), 10 deletions(-)
---
base-commit: 594d905195024b228c962627ae5ae7c17bd582a4
change-id: 20260806-tls-splice-crypto-fix-2a6de3cc0224
Best regards,
--
Chuck Lever <cel@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH net 1/2] net/tls: Fail tls_sw_splice_read() after a failed async decrypt 2026-08-07 0:44 [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt Chuck Lever @ 2026-08-07 0:44 ` Chuck Lever 2026-08-11 10:41 ` Sabrina Dubroca 2026-08-07 0:44 ` [PATCH net 2/2] selftests: tls: cover splice after a failed decrypt Chuck Lever 2026-08-11 13:50 ` [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt patchwork-bot+netdevbpf 2 siblings, 1 reply; 6+ messages in thread From: Chuck Lever @ 2026-08-07 0:44 UTC (permalink / raw) To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman, Shuah Khan Cc: netdev, linux-kernel, linux-kselftest, Chuck Lever When an async decrypt fails, tls_decrypt_done() records the error in ctx->async_wait.err and calls tls_err_abort(), which stores it in sk_err. tls_sw_recvmsg() and tls_sw_read_sock() each read async_wait.err once they hold the reader lock and fail the call: a record that did not authenticate breaks the connection. tls_sw_splice_read() has no such check, and sk_err does not stand in for one. tls_rx_rec_wait() tests sk_err only inside the loop it skips whenever a record is already parsed, and the first reader to reach sock_error() clears it, while async_wait.err persists. A splice therefore keeps delivering records on a connection that recvmsg() and read_sock() refuse to read. Read async_wait.err in tls_sw_splice_read() as the other two readers do. Fixes: f314bfee81b1 ("tls: rx: return the already-copied data on crypto error") Reviewed-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Chuck Lever <cel@kernel.org> --- net/tls/tls_sw.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 62d46736e24b..d1ad31986cf2 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -2014,6 +2014,11 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos, if (err < 0) return err; + /* If crypto failed the connection is broken */ + err = ctx->async_wait.err; + if (err) + goto splice_read_end; + if (!skb_queue_empty(&ctx->rx_list)) { skb = __skb_dequeue(&ctx->rx_list); } else { -- 2.54.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net 1/2] net/tls: Fail tls_sw_splice_read() after a failed async decrypt 2026-08-07 0:44 ` [PATCH net 1/2] net/tls: Fail tls_sw_splice_read() " Chuck Lever @ 2026-08-11 10:41 ` Sabrina Dubroca 0 siblings, 0 replies; 6+ messages in thread From: Sabrina Dubroca @ 2026-08-11 10:41 UTC (permalink / raw) To: Chuck Lever Cc: John Fastabend, Jakub Kicinski, David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman, Shuah Khan, netdev, linux-kernel, linux-kselftest 2026-08-06, 20:44:07 -0400, Chuck Lever wrote: > When an async decrypt fails, tls_decrypt_done() records the error in > ctx->async_wait.err and calls tls_err_abort(), which stores it in > sk_err. tls_sw_recvmsg() and tls_sw_read_sock() each read > async_wait.err once they hold the reader lock and fail the call: a > record that did not authenticate breaks the connection. > > tls_sw_splice_read() has no such check, and sk_err does not stand in > for one. tls_rx_rec_wait() tests sk_err only inside the loop it > skips whenever a record is already parsed, and the first reader to > reach sock_error() clears it, while async_wait.err persists. A > splice therefore keeps delivering records on a connection that > recvmsg() and read_sock() refuse to read. > > Read async_wait.err in tls_sw_splice_read() as the other two readers > do. > > Fixes: f314bfee81b1 ("tls: rx: return the already-copied data on crypto error") > Reviewed-by: Jakub Kicinski <kuba@kernel.org> > Signed-off-by: Chuck Lever <cel@kernel.org> Reviewed-by: Sabrina Dubroca <sd@queasysnail.net> -- Sabrina ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net 2/2] selftests: tls: cover splice after a failed decrypt 2026-08-07 0:44 [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt Chuck Lever 2026-08-07 0:44 ` [PATCH net 1/2] net/tls: Fail tls_sw_splice_read() " Chuck Lever @ 2026-08-07 0:44 ` Chuck Lever 2026-08-11 10:45 ` Sabrina Dubroca 2026-08-11 13:50 ` [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt patchwork-bot+netdevbpf 2 siblings, 1 reply; 6+ messages in thread From: Chuck Lever @ 2026-08-07 0:44 UTC (permalink / raw) To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman, Shuah Khan Cc: netdev, linux-kernel, linux-kselftest, Chuck Lever Nothing in this file splices a socket whose last decrypt failed, so the check that fails tls_sw_splice_read() on a broken connection can be removed without a test noticing. Such a splice hands the application plaintext that recvmsg() and read_sock() already refuse to return. Extend the bad_auth pattern. Corrupt an authenticated record, confirm recvmsg() reports EBADMSG, then splice the same socket and require EBADMSG again. A synchronous decrypt fails again on the still-queued record, so only an async decrypt reaches EBADMSG through the recorded-failure check alone. bad_auth builds the same corrupted record, so its construction moves into a helper the two tests share. Signed-off-by: Chuck Lever <cel@kernel.org> --- tools/testing/selftests/net/tls.c | 75 +++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c index a0e72e402748..9d3cd4fff062 100644 --- a/tools/testing/selftests/net/tls.c +++ b/tools/testing/selftests/net/tls.c @@ -24,6 +24,7 @@ #include "kselftest_harness.h" #define TLS_PAYLOAD_MAX_LEN 16384 +#define TLS_HDR_LEN 5 #define SOL_TLS 282 static int fips_enabled; @@ -2732,28 +2733,83 @@ TEST_F(tls_err, bad_rec) EXPECT_EQ(errno, EAGAIN); } +/* cfd carries a byte stream, so one recv() can return part of a + * record. Take the fragment length from the record header and wait + * for the remainder. + */ +static void tls_send_bad_auth(struct __test_metadata *_metadata, + int fd, int cfd, int fd2) +{ + char buf[128]; + int len; + + memrnd(buf, sizeof(buf) / 2); + ASSERT_EQ(send(fd, buf, sizeof(buf) / 2, 0), sizeof(buf) / 2); + + ASSERT_EQ(recv(cfd, buf, TLS_HDR_LEN, MSG_WAITALL), TLS_HDR_LEN); + + len = ((unsigned char)buf[3] << 8) | (unsigned char)buf[4]; + ASSERT_GT(len, 0); + ASSERT_LE(len, (int)sizeof(buf) - TLS_HDR_LEN); + + ASSERT_EQ(recv(cfd, buf + TLS_HDR_LEN, len, MSG_WAITALL), len); + + buf[TLS_HDR_LEN + len - 1]++; + + ASSERT_EQ(send(fd2, buf, TLS_HDR_LEN + len, 0), TLS_HDR_LEN + len); +} + TEST_F(tls_err, bad_auth) { char buf[128]; - int n; if (self->notls) SKIP(return, "no TLS support"); - memrnd(buf, sizeof(buf) / 2); - EXPECT_EQ(send(self->fd, buf, sizeof(buf) / 2, 0), sizeof(buf) / 2); - n = recv(self->cfd, buf, sizeof(buf), 0); - EXPECT_GT(n, sizeof(buf) / 2); + tls_send_bad_auth(_metadata, self->fd, self->cfd, self->fd2); - buf[n - 1]++; - - EXPECT_EQ(send(self->fd2, buf, n, 0), n); EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1); EXPECT_EQ(errno, EBADMSG); EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1); EXPECT_EQ(errno, EBADMSG); } +/* A record that did not authenticate breaks the connection for every + * reader, splice included. + * + * The two decrypt paths reach that result differently. A synchronous + * decrypt leaves the record parsed, so the splice re-runs the decrypt + * and fails on the record itself; the ctx->async_wait.err check in + * tls_sw_splice_read() is not what stops it. Only an asynchronous + * decrypt, which needs a TLS 1.2 socket and an AEAD advertising + * CRYPTO_ALG_ASYNC, consumes the record before the failure is + * recorded, leaving that check the sole reason the splice fails. + */ +TEST_F(tls_err, bad_auth_splice) +{ + char buf[128]; + ssize_t ret; + int p[2]; + + if (self->notls) + SKIP(return, "no TLS support"); + + tls_send_bad_auth(_metadata, self->fd, self->cfd, self->fd2); + + EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1); + EXPECT_EQ(errno, EBADMSG); + + ASSERT_GE(pipe(p), 0); + + ret = splice(self->cfd2, NULL, p[1], NULL, sizeof(buf), + SPLICE_F_NONBLOCK); + EXPECT_EQ(ret, -1); + EXPECT_EQ(errno, EBADMSG); + + close(p[0]); + close(p[1]); +} + TEST_F(tls_err, bad_in_large_read) { char txt[3][64]; @@ -3009,7 +3065,6 @@ static size_t parse_tls_records(struct __test_metadata *_metadata, { const __u8 *rec = rx_buf; size_t total_plaintext_rx = 0; - const __u8 rec_header_len = 5; while (rec < rx_buf + rx_len) { __u16 record_payload_len; @@ -3029,7 +3084,7 @@ static size_t parse_tls_records(struct __test_metadata *_metadata, /* Plaintext must not exceed the specified limit */ ASSERT_LE(plaintext_len, max_payload_len); - rec += rec_header_len + record_payload_len; + rec += TLS_HDR_LEN + record_payload_len; } return total_plaintext_rx; -- 2.54.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net 2/2] selftests: tls: cover splice after a failed decrypt 2026-08-07 0:44 ` [PATCH net 2/2] selftests: tls: cover splice after a failed decrypt Chuck Lever @ 2026-08-11 10:45 ` Sabrina Dubroca 0 siblings, 0 replies; 6+ messages in thread From: Sabrina Dubroca @ 2026-08-11 10:45 UTC (permalink / raw) To: Chuck Lever Cc: John Fastabend, Jakub Kicinski, David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman, Shuah Khan, netdev, linux-kernel, linux-kselftest 2026-08-06, 20:44:08 -0400, Chuck Lever wrote: > Nothing in this file splices a socket whose last decrypt failed, so > the check that fails tls_sw_splice_read() on a broken connection can > be removed without a test noticing. Such a splice hands the > application plaintext that recvmsg() and read_sock() already refuse > to return. > > Extend the bad_auth pattern. Corrupt an authenticated record, confirm > recvmsg() reports EBADMSG, then splice the same socket and require > EBADMSG again. A synchronous decrypt fails again on the still-queued > record, so only an async decrypt reaches EBADMSG through the > recorded-failure check alone. > > bad_auth builds the same corrupted record, so its construction moves > into a helper the two tests share. > > Signed-off-by: Chuck Lever <cel@kernel.org> > --- > tools/testing/selftests/net/tls.c | 75 +++++++++++++++++++++++++++++++++------ > 1 file changed, 65 insertions(+), 10 deletions(-) Reviewed-by: Sabrina Dubroca <sd@queasysnail.net> (just one nit:) > +/* cfd carries a byte stream, so one recv() can return part of a > + * record. Take the fragment length from the record header and wait > + * for the remainder. > + */ I understand what the function does, but it took me a while to parse this comment. Now I see it applies more to the "MSG_WAITALL/len/MSG_WAITALL" construct than to the overall function (the function's name gives a good enough description of what it does). > +static void tls_send_bad_auth(struct __test_metadata *_metadata, > + int fd, int cfd, int fd2) > +{ > + char buf[128]; > + int len; > + > + memrnd(buf, sizeof(buf) / 2); > + ASSERT_EQ(send(fd, buf, sizeof(buf) / 2, 0), sizeof(buf) / 2); > + > + ASSERT_EQ(recv(cfd, buf, TLS_HDR_LEN, MSG_WAITALL), TLS_HDR_LEN); > + > + len = ((unsigned char)buf[3] << 8) | (unsigned char)buf[4]; > + ASSERT_GT(len, 0); > + ASSERT_LE(len, (int)sizeof(buf) - TLS_HDR_LEN); > + > + ASSERT_EQ(recv(cfd, buf + TLS_HDR_LEN, len, MSG_WAITALL), len); > + > + buf[TLS_HDR_LEN + len - 1]++; > + > + ASSERT_EQ(send(fd2, buf, TLS_HDR_LEN + len, 0), TLS_HDR_LEN + len); > +} -- Sabrina ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt 2026-08-07 0:44 [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt Chuck Lever 2026-08-07 0:44 ` [PATCH net 1/2] net/tls: Fail tls_sw_splice_read() " Chuck Lever 2026-08-07 0:44 ` [PATCH net 2/2] selftests: tls: cover splice after a failed decrypt Chuck Lever @ 2026-08-11 13:50 ` patchwork-bot+netdevbpf 2 siblings, 0 replies; 6+ messages in thread From: patchwork-bot+netdevbpf @ 2026-08-11 13:50 UTC (permalink / raw) To: Chuck Lever Cc: john.fastabend, kuba, sd, davem, edumazet, pabeni, horms, shuah, netdev, linux-kernel, linux-kselftest Hello: This series was applied to netdev/net.git (main) by Paolo Abeni <pabeni@redhat.com>: On Thu, 06 Aug 2026 20:44:06 -0400 you wrote: > tls_sw_recvmsg() and tls_sw_read_sock() both read ctx->async_wait.err > once they hold the reader lock, so a record that failed > authentication fails the call. tls_sw_splice_read() has no such > check. sk_err does not stand in for one. The first reader to reach > sock_error() clears sk_err, while async_wait.err persists. A splice > therefore keeps delivering records on a connection the other two > readers have already refused. > > [...] Here is the summary with links: - [net,1/2] net/tls: Fail tls_sw_splice_read() after a failed async decrypt https://git.kernel.org/netdev/net/c/976df67f463d - [net,2/2] selftests: tls: cover splice after a failed decrypt https://git.kernel.org/netdev/net/c/8a4222973913 You are awesome, thank you! -- Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-11 13:50 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-07 0:44 [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt Chuck Lever 2026-08-07 0:44 ` [PATCH net 1/2] net/tls: Fail tls_sw_splice_read() " Chuck Lever 2026-08-11 10:41 ` Sabrina Dubroca 2026-08-07 0:44 ` [PATCH net 2/2] selftests: tls: cover splice after a failed decrypt Chuck Lever 2026-08-11 10:45 ` Sabrina Dubroca 2026-08-11 13:50 ` [PATCH net 0/2] net/tls: Fail splice after a failed async decrypt patchwork-bot+netdevbpf
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.