tls: fix plaintext sk_msg ring over-fill
Subsystems (5)
v2 cover tls: fix plaintext sk_msg ring over-fill
An unprivileged user can oops the kernel by splicing into a kTLS socket whose open record already has a full plaintext sk_msg ring. Reproduced on net (53658c6f3682) with a stock config, no KASAN.
Patch 2 oopses an unpatched kernel and passes with patch 1 applied.
v2:
- fix the copy path so a full record is never left unpushed, rather than making tls_sw_sendmsg_splice() tolerate a full ring (Sabrina)
- selftest: drop the comments, one splice instead of four, reuse a single pipe, compare the whole blob, sweep 16..44 fragments
v1: https://lore.kernel.org/netdev/[email protected]/
chanyoung (2):
tls: don't leave a full plaintext sk_msg ring unpushed
selftests: tls: add a test for splicing onto a full plaintext record
net/tls/tls_sw.c | 14 ++++++++++++ tools/testing/selftests/net/tls.c | 37 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+)
Signature · 1 line
2.43.0
PATCH v2 1/2 tls: don't leave a full plaintext sk_msg ring unpushed
When the copy path in tls_sw_sendmsg_locked() adds the fragment that fills the plaintext sk_msg ring, it does not set full_record, so the record is left full and unpushed. A later splice() then adds to an already full
ring: sk_msg_page_add() has no fullness check of its own, so sg.end wraps onto sg.start and the ring appears empty. Fragments added after that overwrite live entries, and sg.size no longer matches what is reachable between sg.start and sg.end, so pushing the record runs the scatterwalk off the end of the scatterlist.
An unprivileged user can trigger this on a loopback TCP socket with the "tls" ULP attached:
BUG: kernel NULL pointer dereference, address: 0000000000000008 RIP: 0010:memcpy_from_scatterwalk+0x32/0xc0 Call Trace: skcipher_walk_next+0x1d1/0x2c0 gcm_encrypt_aesni_avx+0x1e9/0x220 bpf_exec_tx_verdict+0x3bb/0x860 tls_sw_sendmsg+0xa1a/0xca0 __sys_sendto+0x1da/0x1f0
Set full_record in the copy path when the ring becomes full, and push a record that is already full on entry to the sendmsg loop.
--- net/tls/tls_sw.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
net/tls/tls_sw.c +14 -0
@@ -832,6 +832,14 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,if (!sk_stream_memory_free (sk)) goto wait_for_sndbuf; +/* open record may be full if we couldn't push it in the last sendmsg call */ +if (sk_msg_full (msg_pl)) { + full_record = true; +sk_msg_trim (sk, msg_en, + msg_pl->sg.size + prot->overhead_size); + goto copied; + } + alloc_encrypted: ret =tls_alloc_encrypted_msg (sk, required_size);if (ret) {
@@ -921,6 +929,12 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg, msg_pl, try_to_copy);if (ret <0 ) goto trim_sgl; + +if (sk_msg_full (msg_pl)) { + full_record = true; +sk_msg_trim (sk, msg_en, + msg_pl->sg.size + prot->overhead_size); + } }/* Open records defined only if successfully copied, otherwise
Signature · 1 line
2.43.0
PATCH v2 2/2 selftests: tls: add a test for splicing onto a full plaintext record
In reply to chanyoungSplicing onto a plaintext sk_msg ring that is already full used to wrap the ring and make the kernel oops in the scatterwalk once the record was pushed.
Only the copy path leaves the ring full without pushing it, so splice until the ring is one fragment short, add the last fragment with a one-byte MSG_MORE send, and splice once more before pushing the record.
CONFIG_MAX_SKB_FRAGS is 17..45, so that last fragment follows between 16 and 44 splices; sweep that range to trigger the bug on any build.
--- tools/testing/selftests/net/tls.c | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+)
tools/testing/selftests/net/tls.c +37 -0
@@ -835,6 +835,43 @@ TEST_F(tls, send_and_splice)EXPECT_EQ (memcmp (mem_send, mem_recv, send_len),0 ); } +TEST_F(tls, splice_onto_full_record) +{ +char mem_send[4608 ]; +char mem_recv[4608 ]; +int frag_len =100 ; +int nfrags, i, off; +int p[2 ]; + +memrnd (mem_send,sizeof (mem_send)); +ASSERT_GE (pipe (p),0 ); + +for (nfrags =16 ; nfrags <=44 ; nfrags++) { +for (i =0 , off =0 ; i < nfrags; i++, off += frag_len) { +EXPECT_EQ (write (p[1 ], mem_send + off, frag_len), frag_len); +EXPECT_EQ (splice (p[0 ],NULL , self->fd,NULL , frag_len, +SPLICE_F_MORE ), frag_len); + } + +EXPECT_EQ (send (self->fd, mem_send + off,1 ,MSG_MORE ),1 ); + off++; + +EXPECT_EQ (write (p[1 ], mem_send + off, frag_len), frag_len); +EXPECT_EQ (splice (p[0 ],NULL , self->fd,NULL , frag_len, +SPLICE_F_MORE ), frag_len); + off += frag_len; + +EXPECT_EQ (send (self->fd, mem_send + off,1 ,0 ),1 ); + off++; + +EXPECT_EQ (recv (self->cfd, mem_recv, off,MSG_WAITALL ), off); +EXPECT_EQ (memcmp (mem_send, mem_recv, off),0 ); + } + +close (p[0 ]); +close (p[1 ]); +} + TEST_F(tls, splice_to_pipe) {int send_len =TLS_PAYLOAD_MAX_LEN ;
Signature · 1 line
2.43.0
This series was applied to netdev/net.git (main) by Jakub Kicinski <[email protected]>:
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <[email protected]>:
Quoted 6 lines · On Tue, 4 Aug 2026 14:28:34 +0900 you wrote:
An unprivileged user can oops the kernel by splicing into a kTLS socket whose open record already has a full plaintext
sk_msgring. Reproduced on net (53658c6f3682) with a stock config, no KASAN.Patch 2 oopses an unpatched kernel and passes with patch 1 applied.
Quoted 8 lines
v2:
- fix the copy path so a full record is never left unpushed, rather than makingtls_sw_sendmsg_splice()tolerate a full ring (Sabrina)
- selftest: drop the comments, one splice instead of four, reuse a single pipe, compare the whole blob, sweep 16..44 fragments
v1: https://lore.kernel.org/netdev/[email protected]/[...]
Here is the summary with links:
- [net,v2,1/2] tls: don't leave a full plaintext sk_msg ring unpushed
https://git.kernel.org/netdev/net/c/7bca91d63341 - [net,v2,2/2] selftests: tls: add a test for splicing onto a full plaintext record https://git.kernel.org/netdev/net/c/3834e079d67f
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html