CVE-2022-29582: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition') (CWE-362)
value: In the Linux kernel before 5.17.3, fs/io_uring.c has a use-after-free due to a race condition in io_uring timeouts. This can be triggered by a local user who has no access to any user namespace; however, the race condition perhaps can only be exploited infrequently.
CWE description: The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
CVSS v3 base score: 7
Published: 2022-04-22T16:15Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: fs/io_uring.c
Commit date: 2022-04-08 14:50:05 -0600
Commit message: io_uring: fix race between timeout flush and removal

io_flush_timeouts() assumes the timeout isn't in progress of triggering
or being removed/canceled, so it unconditionally removes it from the
timeout list and attempts to cancel it.

Leave it on the list and let the normal timeout cancelation take care
of it.

Cc: stable@vger.kernel.org # 5.5+
Signed-off-by: Jens Axboe <axboe@kernel.dk>

Diff: diff --git a/fs/io_uring.c b/fs/io_uring.c
index fafd1ca4780b..659f8ecba5b7 100644
--- a/fs/io_uring.c
+++ b/fs/io_uring.c
@@ -1734,16 +1734,15 @@ static __cold void io_queue_deferred(struct io_ring_ctx *ctx)
 
 static __cold void io_flush_timeouts(struct io_ring_ctx *ctx)
 	__must_hold(&ctx->completion_lock)
 {
 	u32 seq = ctx->cached_cq_tail - atomic_read(&ctx->cq_timeouts);
+	struct io_kiocb *req, *tmp;
 
 	spin_lock_irq(&ctx->timeout_lock);
-	while (!list_empty(&ctx->timeout_list)) {
+	list_for_each_entry_safe(req, tmp, &ctx->timeout_list, timeout.list) {
 		u32 events_needed, events_got;
-		struct io_kiocb *req = list_first_entry(&ctx->timeout_list,
-						struct io_kiocb, timeout.list);
 
 		if (io_is_timeout_noseq(req))
 			break;
 
 		/*
@@ -1756,11 +1755,10 @@ static __cold void io_flush_timeouts(struct io_ring_ctx *ctx)
 		events_needed = req->timeout.target_seq - ctx->cq_last_tm_flush;
 		events_got = seq - ctx->cq_last_tm_flush;
 		if (events_got < events_needed)
 			break;
 
-		list_del_init(&req->timeout.list);
 		io_kill_timeout(req, 0);
 	}
 	ctx->cq_last_tm_flush = seq;
 	spin_unlock_irq(&ctx->timeout_lock);
 }
@@ -6626,10 +6624,11 @@ static int io_timeout_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe,
 		return -EFAULT;
 
 	if (data->ts.tv_sec < 0 || data->ts.tv_nsec < 0)
 		return -EINVAL;
 
+	INIT_LIST_HEAD(&req->timeout.list);
 	data->mode = io_translate_timeout_mode(flags);
 	hrtimer_init(&data->timer, io_timeout_get_clock(data), data->mode);
 
 	if (is_timeout_link) {
 		struct io_submit_link *link = &req->ctx->submit_state.link;

Vulnerable code: 	while (!list_empty(&ctx->timeout_list)) {
		struct io_kiocb *req = list_first_entry(&ctx->timeout_list,
						struct io_kiocb, timeout.list);
		list_del_init(&req->timeout.list);
Fixed code: 	struct io_kiocb *req, *tmp;
	list_for_each_entry_safe(req, tmp, &ctx->timeout_list, timeout.list) {
	INIT_LIST_HEAD(&req->timeout.list);