* [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
2026-08-07 0:44 ` [PATCH net 2/2] selftests: tls: cover splice after a failed decrypt Chuck Lever
0 siblings, 2 replies; 5+ 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] 5+ 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
1 sibling, 1 reply; 5+ 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] 5+ 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
1 sibling, 1 reply; 5+ 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] 5+ 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; 5+ 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] 5+ 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; 5+ 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] 5+ messages in thread
end of thread, other threads:[~2026-08-11 10:45 UTC | newest]
Thread overview: 5+ 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
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.