[PATCH v2] io_uring/net: don't check MSG_CTRUNC for IORING_OP_RECV

From: Hannes Furmans

Date: Fri Feb 27 2026 - 11:28:19 EST


IORING_OP_RECV sets up the msghdr with msg_control=NULL and
msg_controllen=0, as it has no cmsg support. Any socket layer that
calls put_cmsg() will find no buffer space and set MSG_CTRUNC in
msg_flags. This is expected — the caller didn't ask for control data.

However, io_recv checks:

if ((flags & MSG_WAITALL) && (msg_flags & (MSG_TRUNC | MSG_CTRUNC)))
req_set_fail(req);

This sets REQ_F_FAIL on a fully successful recv (ret >= min_ret) when
MSG_CTRUNC is set, which causes io_disarm_next() to cancel all linked
operations with -ECANCELED. The recv CQE shows the full requested byte
count, yet linked operations are cancelled.

This is triggered by kTLS, which calls put_cmsg(SOL_TLS,
TLS_GET_RECORD_TYPE) for every record in tls_record_content_type()
(tls_sw.c), but it affects any protocol that delivers cmsg data on
the kernel side.

The MSG_CTRUNC check was introduced by commit 0031275d119e ("io_uring:
call req_set_fail_links() on short send[msg]()/recv[msg]() with
MSG_WAITALL") whose commit message states "For IORING_OP_RECVMSG we
also check for the MSG_TRUNC and MSG_CTRUNC flags", but the code
applied the check to IORING_OP_RECV as well. MSG_CTRUNC is meaningful
for IORING_OP_RECVMSG where the user provides a cmsg buffer —
truncation there means lost metadata. It is meaningless for
IORING_OP_RECV which never provides a cmsg buffer.

Remove MSG_CTRUNC from the io_recv check. The io_recvmsg check is
left unchanged as MSG_CTRUNC is meaningful there.

Fixes: 0031275d119e ("io_uring: call req_set_fail_links() on short send[msg]()/recv[msg]() with MSG_WAITALL")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Hannes Furmans <hannes@xxxxxxxxxxxx>
---
v2: v1 incorrectly guarded req_set_fail() for all done_io > 0 cases.
Stefan Metzmacher correctly pointed out that short MSG_WAITALL
reads should still sever the link chain.

Root-caused via ftrace + msg_flags inspection on a real kTLS
connection (TLS 1.3, AES-128-GCM, S3 download):

ftrace shows io_uring_fail_link firing immediately after
io_uring_complete with result=67108864 (full 64MB), from io-wq:

iou-wrk-52242 io_uring_complete: req ..., result 67108864
iou-wrk-52242 io_uring_fail_link: opcode RECV, link ...

A debug recvmsg on the same kTLS socket shows:

recvmsg: ret=67108864 msg_flags=0x88 (MSG_EOR | MSG_CTRUNC)

MSG_CTRUNC is always set because kTLS calls put_cmsg() but
IORING_OP_RECV provides no cmsg buffer.

io_uring/net.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/io_uring/net.c b/io_uring/net.c
index 8576c6cb2236..8baaf74e8f8d 100644
--- a/io_uring/net.c
+++ b/io_uring/net.c
@@ -1221,7 +1221,7 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags)
if (ret == -ERESTARTSYS)
ret = -EINTR;
req_set_fail(req);
- } else if ((flags & MSG_WAITALL) && (kmsg->msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC))) {
+ } else if ((flags & MSG_WAITALL) && (kmsg->msg.msg_flags & MSG_TRUNC)) {
out_free:
req_set_fail(req);
}
--
2.53.0