| From 41b70df5b38bc80967d2e0ed55cc3c3896bba781 Mon Sep 17 00:00:00 2001 |
| From: Jens Axboe <axboe@kernel.dk> |
| Date: Tue, 12 Aug 2025 08:30:11 -0600 |
| Subject: io_uring/net: commit partial buffers on retry |
| |
| From: Jens Axboe <axboe@kernel.dk> |
| |
| commit 41b70df5b38bc80967d2e0ed55cc3c3896bba781 upstream. |
| |
| Ring provided buffers are potentially only valid within the single |
| execution context in which they were acquired. io_uring deals with this |
| and invalidates them on retry. But on the networking side, if |
| MSG_WAITALL is set, or if the socket is of the streaming type and too |
| little was processed, then it will hang on to the buffer rather than |
| recycle or commit it. This is problematic for two reasons: |
| |
| 1) If someone unregisters the provided buffer ring before a later retry, |
| then the req->buf_list will no longer be valid. |
| |
| 2) If multiple sockers are using the same buffer group, then multiple |
| receives can consume the same memory. This can cause data corruption |
| in the application, as either receive could land in the same |
| userspace buffer. |
| |
| Fix this by disallowing partial retries from pinning a provided buffer |
| across multiple executions, if ring provided buffers are used. |
| |
| Cc: stable@vger.kernel.org |
| Reported-by: pt x <superman.xpt@gmail.com> |
| Fixes: c56e022c0a27 ("io_uring: add support for user mapped provided buffer ring") |
| Signed-off-by: Jens Axboe <axboe@kernel.dk> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| io_uring/net.c | 27 +++++++++++++++------------ |
| 1 file changed, 15 insertions(+), 12 deletions(-) |
| |
| --- a/io_uring/net.c |
| +++ b/io_uring/net.c |
| @@ -498,6 +498,15 @@ static int io_bundle_nbufs(struct io_asy |
| return nbufs; |
| } |
| |
| +static int io_net_kbuf_recyle(struct io_kiocb *req, |
| + struct io_async_msghdr *kmsg, int len) |
| +{ |
| + req->flags |= REQ_F_BL_NO_RECYCLE; |
| + if (req->flags & REQ_F_BUFFERS_COMMIT) |
| + io_kbuf_commit(req, req->buf_list, len, io_bundle_nbufs(kmsg, len)); |
| + return -EAGAIN; |
| +} |
| + |
| static inline bool io_send_finish(struct io_kiocb *req, int *ret, |
| struct io_async_msghdr *kmsg, |
| unsigned issue_flags) |
| @@ -566,8 +575,7 @@ int io_sendmsg(struct io_kiocb *req, uns |
| kmsg->msg.msg_controllen = 0; |
| kmsg->msg.msg_control = NULL; |
| sr->done_io += ret; |
| - req->flags |= REQ_F_BL_NO_RECYCLE; |
| - return -EAGAIN; |
| + return io_net_kbuf_recyle(req, kmsg, ret); |
| } |
| if (ret == -ERESTARTSYS) |
| ret = -EINTR; |
| @@ -664,8 +672,7 @@ retry_bundle: |
| sr->len -= ret; |
| sr->buf += ret; |
| sr->done_io += ret; |
| - req->flags |= REQ_F_BL_NO_RECYCLE; |
| - return -EAGAIN; |
| + return io_net_kbuf_recyle(req, kmsg, ret); |
| } |
| if (ret == -ERESTARTSYS) |
| ret = -EINTR; |
| @@ -1068,8 +1075,7 @@ retry_multishot: |
| } |
| if (ret > 0 && io_net_retry(sock, flags)) { |
| sr->done_io += ret; |
| - req->flags |= REQ_F_BL_NO_RECYCLE; |
| - return -EAGAIN; |
| + return io_net_kbuf_recyle(req, kmsg, ret); |
| } |
| if (ret == -ERESTARTSYS) |
| ret = -EINTR; |
| @@ -1211,8 +1217,7 @@ retry_multishot: |
| sr->len -= ret; |
| sr->buf += ret; |
| sr->done_io += ret; |
| - req->flags |= REQ_F_BL_NO_RECYCLE; |
| - return -EAGAIN; |
| + return io_net_kbuf_recyle(req, kmsg, ret); |
| } |
| if (ret == -ERESTARTSYS) |
| ret = -EINTR; |
| @@ -1441,8 +1446,7 @@ int io_send_zc(struct io_kiocb *req, uns |
| zc->len -= ret; |
| zc->buf += ret; |
| zc->done_io += ret; |
| - req->flags |= REQ_F_BL_NO_RECYCLE; |
| - return -EAGAIN; |
| + return io_net_kbuf_recyle(req, kmsg, ret); |
| } |
| if (ret == -ERESTARTSYS) |
| ret = -EINTR; |
| @@ -1502,8 +1506,7 @@ int io_sendmsg_zc(struct io_kiocb *req, |
| |
| if (ret > 0 && io_net_retry(sock, flags)) { |
| sr->done_io += ret; |
| - req->flags |= REQ_F_BL_NO_RECYCLE; |
| - return -EAGAIN; |
| + return io_net_kbuf_recyle(req, kmsg, ret); |
| } |
| if (ret == -ERESTARTSYS) |
| ret = -EINTR; |