Skip to content

Commit 2faf852

Browse files
committed
io_uring: cleanup fixed file data table references
syzbot reports a use-after-free in io_ring_file_ref_switch() when it tries to switch back to percpu mode. When we put the final reference to the table by calling percpu_ref_kill_and_confirm(), we don't want the zero reference to queue async work for flushing the potentially queued up items. We currently do a few flush_work(), but they merely paper around the issue, since the work item may not have been queued yet depending on the when the percpu-ref callback gets run. Coming into the file unregister, we know we have the ring quiesced. io_ring_file_ref_switch() can check for whether or not the ref is dying or not, and not queue anything async at that point. Once the ref has been confirmed killed, flush any potential items manually. Reported-by: [email protected] Fixes: 05f3fb3 ("io_uring: avoid ring quiesce for fixed file set unregister and update") Signed-off-by: Jens Axboe <[email protected]>
1 parent df069d8 commit 2faf852

File tree

1 file changed

+19
-13
lines changed

1 file changed

+19
-13
lines changed

fs/io_uring.c

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ static int __io_sqe_files_update(struct io_ring_ctx *ctx,
753753
struct io_uring_files_update *ip,
754754
unsigned nr_args);
755755
static int io_grab_files(struct io_kiocb *req);
756+
static void io_ring_file_ref_flush(struct fixed_file_data *data);
756757

757758
static struct kmem_cache *req_cachep;
758759

@@ -5261,15 +5262,10 @@ static int io_sqe_files_unregister(struct io_ring_ctx *ctx)
52615262
if (!data)
52625263
return -ENXIO;
52635264

5264-
/* protect against inflight atomic switch, which drops the ref */
5265-
percpu_ref_get(&data->refs);
5266-
/* wait for existing switches */
5267-
flush_work(&data->ref_work);
52685265
percpu_ref_kill_and_confirm(&data->refs, io_file_ref_kill);
5269-
wait_for_completion(&data->done);
5270-
percpu_ref_put(&data->refs);
5271-
/* flush potential new switch */
52725266
flush_work(&data->ref_work);
5267+
wait_for_completion(&data->done);
5268+
io_ring_file_ref_flush(data);
52735269
percpu_ref_exit(&data->refs);
52745270

52755271
__io_sqe_files_unregister(ctx);
@@ -5507,14 +5503,11 @@ struct io_file_put {
55075503
struct completion *done;
55085504
};
55095505

5510-
static void io_ring_file_ref_switch(struct work_struct *work)
5506+
static void io_ring_file_ref_flush(struct fixed_file_data *data)
55115507
{
55125508
struct io_file_put *pfile, *tmp;
5513-
struct fixed_file_data *data;
55145509
struct llist_node *node;
55155510

5516-
data = container_of(work, struct fixed_file_data, ref_work);
5517-
55185511
while ((node = llist_del_all(&data->put_llist)) != NULL) {
55195512
llist_for_each_entry_safe(pfile, tmp, node, llist) {
55205513
io_ring_file_put(data->ctx, pfile->file);
@@ -5524,7 +5517,14 @@ static void io_ring_file_ref_switch(struct work_struct *work)
55245517
kfree(pfile);
55255518
}
55265519
}
5520+
}
5521+
5522+
static void io_ring_file_ref_switch(struct work_struct *work)
5523+
{
5524+
struct fixed_file_data *data;
55275525

5526+
data = container_of(work, struct fixed_file_data, ref_work);
5527+
io_ring_file_ref_flush(data);
55285528
percpu_ref_get(&data->refs);
55295529
percpu_ref_switch_to_percpu(&data->refs);
55305530
}
@@ -5535,8 +5535,14 @@ static void io_file_data_ref_zero(struct percpu_ref *ref)
55355535

55365536
data = container_of(ref, struct fixed_file_data, refs);
55375537

5538-
/* we can't safely switch from inside this context, punt to wq */
5539-
queue_work(system_wq, &data->ref_work);
5538+
/*
5539+
* We can't safely switch from inside this context, punt to wq. If
5540+
* the table ref is going away, the table is being unregistered.
5541+
* Don't queue up the async work for that case, the caller will
5542+
* handle it.
5543+
*/
5544+
if (!percpu_ref_is_dying(&data->refs))
5545+
queue_work(system_wq, &data->ref_work);
55405546
}
55415547

55425548
static int io_sqe_files_register(struct io_ring_ctx *ctx, void __user *arg,

0 commit comments

Comments
 (0)