]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
io_uring/poll: correctly handle io_poll_add() return value on update
authorJens Axboe <axboe@kernel.dk>
Fri, 13 Mar 2026 21:50:21 +0000 (15:50 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 18 Apr 2026 08:33:39 +0000 (10:33 +0200)
Commit 84230ad2d2afbf0c44c32967e525c0ad92e26b4e upstream.

When the core of io_uring was updated to handle completions
consistently and with fixed return codes, the POLL_REMOVE opcode
with updates got slightly broken. If a POLL_ADD is pending and
then POLL_REMOVE is used to update the events of that request, if that
update causes the POLL_ADD to now trigger, then that completion is lost
and a CQE is never posted.

Additionally, ensure that if an update does cause an existing POLL_ADD
to complete, that the completion value isn't always overwritten with
-ECANCELED. For that case, whatever io_poll_add() set the value to
should just be retained.

Cc: stable@vger.kernel.org
Fixes: 97b388d70b53 ("io_uring: handle completions in the core")
Reported-by: syzbot+641eec6b7af1f62f2b99@syzkaller.appspotmail.com
Tested-by: syzbot+641eec6b7af1f62f2b99@syzkaller.appspotmail.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
io_uring/io_uring.c

index 04e4b1e6a5b85423cbc3638ae069b53d6a816eea..38decfc1a914ac4db31efd1798e29d4d6c344abb 100644 (file)
@@ -6127,7 +6127,7 @@ static int io_poll_add_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe
        return 0;
 }
 
-static int io_poll_add(struct io_kiocb *req, unsigned int issue_flags)
+static int __io_poll_add(struct io_kiocb *req, unsigned int issue_flags)
 {
        struct io_poll_iocb *poll = &req->poll;
        struct io_poll_table ipt;
@@ -6139,11 +6139,21 @@ static int io_poll_add(struct io_kiocb *req, unsigned int issue_flags)
        if (!ret && ipt.error)
                req_set_fail(req);
        ret = ret ?: ipt.error;
-       if (ret)
+       if (ret > 0) {
                __io_req_complete(req, issue_flags, ret, 0);
+               return ret;
+       }
        return 0;
 }
 
+static int io_poll_add(struct io_kiocb *req, unsigned int issue_flags)
+{
+       int ret;
+
+       ret = __io_poll_add(req, issue_flags);
+       return ret < 0 ? ret : 0;
+}
+
 static int io_poll_update(struct io_kiocb *req, unsigned int issue_flags)
 {
        struct io_ring_ctx *ctx = req->ctx;
@@ -6159,6 +6169,7 @@ static int io_poll_update(struct io_kiocb *req, unsigned int issue_flags)
                ret = preq ? -EALREADY : -ENOENT;
                goto out;
        }
+       preq->result = -ECANCELED;
        spin_unlock(&ctx->completion_lock);
 
        if (req->poll_update.update_events || req->poll_update.update_user_data) {
@@ -6171,16 +6182,17 @@ static int io_poll_update(struct io_kiocb *req, unsigned int issue_flags)
                if (req->poll_update.update_user_data)
                        preq->user_data = req->poll_update.new_user_data;
 
-               ret2 = io_poll_add(preq, issue_flags);
+               ret2 = __io_poll_add(preq, issue_flags);
                /* successfully updated, don't complete poll request */
                if (!ret2)
                        goto out;
+               preq->result = ret2;
+
        }
-       req_set_fail(preq);
-       io_req_complete(preq, -ECANCELED);
+       if (preq->result < 0)
+               req_set_fail(preq);
+       io_req_complete(preq, preq->result);
 out:
-       if (ret < 0)
-               req_set_fail(req);
        /* complete update request, we're done with it */
        io_req_complete(req, ret);
        io_ring_submit_unlock(ctx, !(issue_flags & IO_URING_F_NONBLOCK));