[PATCH v2] bcache: fix UAF in cached_dev_free and safely flush/destroy
liequan che <[email protected]> Thu, 13 Nov 2025 19:38:11 +0800
| Newsgroups | org.kernel.vger.linux-bcache,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAAsfc_pafORaG_PrVpOB9GBK+YCjdzJMd2Ww=ya2PbcPkw04+w@mail.gmail.com> |
stop writeback thread and rate-update work exactly once across teardown paths, - Add STOP_THREAD_ONCE() and use it at all three places that stop dc->writeback_thread: cached_dev_detach_finish(), cached_dev_free(), and the bch_cached_dev_attach() error path. - In cached_dev_detach_finish(), also clear WB_RUNNING and cancel the periodic writeback-rate delayed work to avoid a UAF window after detach is initiated. - Keep the per-dc writeback workqueue flush/destroy in the writeback thread exit tail, avoiding double-destroy. Signed-off-by: cheliequan <[email protected]> --- drivers/md/bcache/bcache.h | 11 +++++++++++ drivers/md/bcache/super.c | 14 ++++++-------- drivers/md/bcache/writeback.c | 7 +++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/drivers/md/bcache/bcache.h b/drivers/md/bcache/bcache.h index 1d33e40d26ea..66dc5dca5c20 100644 --- a/drivers/md/bcache/bcache.h +++ b/drivers/md/bcache/bcache.h @@ -961,6 +961,17 @@ static inline void wait_for_kthread_stop(void) } } +/* + * Stop a kthread exactly once by taking ownership of the pointer. + * Safe against concurrent callers and against already-stopped threads. + */ +#define STOP_THREAD_ONCE(dc, member) \ + do { \ + struct task_struct *t__ = xchg(&(dc)->member, NULL); \ + if (t__ && !IS_ERR(t__)) \ + kthread_stop(t__); \ + } while (0) + /* Forward declarations */ void bch_count_backing_io_errors(struct cached_dev *dc, struct bio *bio); diff --git a/drivers/md/bcache/super.c b/drivers/md/bcache/super.c index 1492c8552255..b4da0a505d4a 100644 --- a/drivers/md/bcache/super.c +++ b/drivers/md/bcache/super.c @@ -1143,8 +1143,7 @@ static void cached_dev_detach_finish(struct work_struct *w) cancel_writeback_rate_update_dwork(dc); if (!IS_ERR_OR_NULL(dc->writeback_thread)) { - kthread_stop(dc->writeback_thread); - dc->writeback_thread = NULL; + STOP_THREAD_ONCE(dc, writeback_thread); } mutex_lock(&bch_register_lock); @@ -1308,8 +1307,9 @@ int bch_cached_dev_attach(struct cached_dev *dc, struct cache_set *c, * created previously in bch_cached_dev_writeback_start() * have to be stopped manually here. */ - kthread_stop(dc->writeback_thread); - cancel_writeback_rate_update_dwork(dc); + if (test_and_clear_bit(BCACHE_DEV_WB_RUNNING, &dc->disk.flags)) + cancel_writeback_rate_update_dwork(dc); + STOP_THREAD_ONCE(dc, writeback_thread); pr_err("Couldn't run cached device %pg\n", dc->bdev); return ret; } @@ -1349,10 +1349,8 @@ static CLOSURE_CALLBACK(cached_dev_free) if (test_and_clear_bit(BCACHE_DEV_WB_RUNNING, &dc->disk.flags)) cancel_writeback_rate_update_dwork(dc); - if (!IS_ERR_OR_NULL(dc->writeback_thread)) - kthread_stop(dc->writeback_thread); - if (!IS_ERR_OR_NULL(dc->status_update_thread)) - kthread_stop(dc->status_update_thread); + STOP_THREAD_ONCE(dc, writeback_thread); + STOP_THREAD_ONCE(dc, status_update_thread); mutex_lock(&bch_register_lock); diff --git a/drivers/md/bcache/writeback.c b/drivers/md/bcache/writeback.c index 302e75f1fc4b..50e67a784acd 100644 --- a/drivers/md/bcache/writeback.c +++ b/drivers/md/bcache/writeback.c @@ -741,6 +741,7 @@ static int bch_writeback_thread(void *arg) struct cached_dev *dc = arg; struct cache_set *c = dc->disk.c; bool searched_full_index; + struct workqueue_struct *wq = NULL; bch_ratelimit_reset(&dc->writeback_rate); @@ -832,8 +833,10 @@ static int bch_writeback_thread(void *arg) } } - if (dc->writeback_write_wq) - destroy_workqueue(dc->writeback_write_wq); + wq = xchg(&dc->writeback_write_wq, NULL); + if (wq) { + destroy_workqueue(wq); + } cached_dev_put(dc); wait_for_kthread_stop(); -- 2.25.1