io_uring/timeout: no ABS update of multishot timeouts

IORING_TIMEOUT_MULTISHOT can't be combined with IORING_TIMEOUT_ABS, prep
rejects that, as the request is armed again with the same value each time
it fires. IORING_TIMEOUT_UPDATE doesn't check and takes an absolute time
for a multishot timeout. The first expiry is as asked for, but
io_timeout_complete() then arms the timer with ->mode of the original
request, i.e. it uses the absolute time as the interval. With
CLOCK_MONOTONIC the next expiry is an uptime away, with CLOCK_REALTIME
more than fifty years.

Fail such an update with -EINVAL, and do so before the timeout is
touched.

Fixes: ea97f6c855 ("io_uring: add support for multishot timeouts")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Link: https://patch.msgid.link/20261015-b4-io-uring-fixes-v1-41-c1556f5d7bf5@toxicpanda.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Josef Bacik 2026-10-15 19:31:05 +00:00 committed by Jens Axboe
parent 8444de681a
commit b088ae34d4

View File

@ -321,22 +321,29 @@ static enum hrtimer_restart io_timeout_fn(struct hrtimer *timer)
return HRTIMER_NORESTART;
}
static struct io_kiocb *io_timeout_find(struct io_ring_ctx *ctx,
struct io_cancel_data *cd)
__must_hold(&ctx->timeout_lock)
{
struct io_timeout *timeout;
list_for_each_entry(timeout, &ctx->timeout_list, list) {
struct io_kiocb *req = cmd_to_io_kiocb(timeout);
if (io_cancel_req_match(req, cd))
return req;
}
return NULL;
}
static struct io_kiocb *io_timeout_extract(struct io_ring_ctx *ctx,
struct io_cancel_data *cd)
__must_hold(&ctx->timeout_lock)
{
struct io_kiocb *req = io_timeout_find(ctx, cd);
struct io_timeout *timeout;
struct io_timeout_data *io;
struct io_kiocb *req = NULL;
list_for_each_entry(timeout, &ctx->timeout_list, list) {
struct io_kiocb *tmp = cmd_to_io_kiocb(timeout);
if (io_cancel_req_match(tmp, cd)) {
req = tmp;
break;
}
}
if (!req)
return ERR_PTR(-ENOENT);
@ -470,14 +477,21 @@ static int io_timeout_update(struct io_ring_ctx *ctx, __u64 user_data,
__must_hold(&ctx->timeout_lock)
{
struct io_cancel_data cd = { .ctx = ctx, .data = user_data, };
struct io_kiocb *req = io_timeout_extract(ctx, &cd);
struct io_kiocb *req = io_timeout_find(ctx, &cd);
struct io_timeout *timeout = io_kiocb_to_cmd(req, struct io_timeout);
struct io_timeout_data *data;
if (IS_ERR(req))
return PTR_ERR(req);
if (!req)
return -ENOENT;
data = req->async_data;
/* multishot requests only make sense with rel values */
if ((data->flags & IORING_TIMEOUT_MULTISHOT) && mode == HRTIMER_MODE_ABS)
return -EINVAL;
req = io_timeout_extract(ctx, &cd);
if (IS_ERR(req))
return PTR_ERR(req);
/* for a multishot request this isn't a sequence, keep the repeats */
if (!(data->flags & IORING_TIMEOUT_MULTISHOT))
timeout->off = 0; /* noseq */