From: Arjan van de Ven Subject: [PATCH] i2c: core: remove adapter from IDR before device_unregister() This patch is based on a WARNING as reported by syzbot at https://lore.kernel.org/r/69e93024.a00a0220.17a17.0031.GAE@google.com There is a race between i2c_get_adapter() and i2c_del_adapter(). i2c_del_adapter() calls device_unregister(&adap->dev), which drops the device refcount to zero when there are no other holders, then waits for the release completion, and only afterwards calls idr_remove() under core_lock. During the window between device_unregister() completing and idr_remove() being called, a concurrent i2c_get_adapter() can: 1. Take core_lock 2. idr_find() -- returns non-NULL (adapter still in IDR) 3. try_module_get() -- succeeds 4. get_device(&adapter->dev) -- CRASH: kobject refcount is already 0 This triggers "refcount_t: addition on 0; use-after-free" in refcount_warn_saturate() (lib/refcount.c:25). The root issue is that get_device() calls kobject_get(), which calls kref_get() unconditionally without checking whether the refcount has already reached zero. Move the idr_remove() call to before device_unregister(). Once the adapter is removed from the IDR under core_lock, any subsequent call to i2c_get_adapter() will receive NULL from idr_find() and return -ENODEV. Callers that had already obtained a device reference before the idr_remove() hold that reference legitimately; the existing wait_for_completion() machinery correctly waits for them to call i2c_put_adapter() before the adapter structure is torn down. Reported-by: syzbot+c0291c8c9aaa473c7721@syzkaller.appspotmail.com Link: https://lore.kernel.org/r/69e93024.a00a0220.17a17.0031.GAE@google.com Fixes: 611e12ea0f12 ("i2c: core: manage i2c bus device refcount in i2c_[get|put]_adapter") Assisted-by: patcher:claude-sonnet-4.6 linux-kernel-oops-x86. Signed-off-by: Arjan van de Ven Cc: Wolfram Sang Cc: linux-i2c@vger.kernel.org Cc: linux-kernel@vger.kernel.org --- drivers/i2c/i2c-core-base.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 9c46147e3506..f10251055c2b 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1796,6 +1796,15 @@ void i2c_del_adapter(struct i2c_adapter *adap) /* device name is gone after device_unregister */ dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name); + /* + * Remove from IDR before device_unregister() so that a concurrent + * i2c_get_adapter() cannot find this adapter (via idr_find) and call + * get_device() on a kobject whose refcount has already reached zero. + */ + mutex_lock(&core_lock); + idr_remove(&i2c_adapter_idr, adap->nr); + mutex_unlock(&core_lock); + pm_runtime_disable(&adap->dev); i2c_host_notify_irq_teardown(adap); @@ -1813,11 +1822,6 @@ void i2c_del_adapter(struct i2c_adapter *adap) device_unregister(&adap->dev); wait_for_completion(&adap->dev_released); - /* free bus id */ - mutex_lock(&core_lock); - idr_remove(&i2c_adapter_idr, adap->nr); - mutex_unlock(&core_lock); - /* Clear the device structure in case this adapter is ever going to be added again */ memset(&adap->dev, 0, sizeof(adap->dev)); -- 2.47.2