All of lore.kernel.org
 help / color / mirror / Atom feed
From: Arjan van de Ven <arjan@linux.intel.com>
To: linux-i2c@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, syzkaller-bugs@googlegroups.com,
	wsa+renesas@sang-engineering.com,
	syzbot+c0291c8c9aaa473c7721@syzkaller.appspotmail.com
Subject: Re: [syzbot] [i2c?] WARNING: refcount bug in i2c_get_adapter (2)
Date: Sat, 25 Apr 2026 14:45:15 -0700	[thread overview]
Message-ID: <20260425214530.424398-1-arjan@linux.intel.com> (raw)
In-Reply-To: <69e93024.a00a0220.17a17.0031.GAE@google.com>

This email is created by automation to help kernel developers
deal with a large volume of AI generated bug reports by decoding
oopses into more actionable information.


Decoded Backtrace

0. refcount_warn_saturate -- lib/refcount.c:25 (crash site)

    11  #define REFCOUNT_WARN(str) WARN_ONCE(1, "refcount_t: " str ".\n")
    12
    13  void refcount_warn_saturate(refcount_t *r,
    13                               enum refcount_saturation_type t)
    14  {
    15      refcount_set(r, REFCOUNT_SATURATED);
    16
    17      switch (t) {
    18      case REFCOUNT_ADD_NOT_ZERO_OVF:
    19      case REFCOUNT_ADD_OVF:
    20          REFCOUNT_WARN("saturated; leaking memory");
    21          break;
    22      case REFCOUNT_ADD_UAF:
    23      case REFCOUNT_SUB_UAF:
->  25          REFCOUNT_WARN("addition on 0; use-after-free");
    25          break;
    26      case REFCOUNT_DEC_LEAK:
    27          REFCOUNT_WARN("decrement hit 0; leaking memory");
    28          break;
    29      default:
    30          REFCOUNT_WARN("unknown saturation event!?");
    31      }
    32  }

RBX = 0x2 = REFCOUNT_ADD_UAF, confirming the switch branch taken.


5. kobject_get -- lib/kobject.c:643

   636  struct kobject *kobject_get(struct kobject *kobj)
   637  {
   638      if (kobj) {
   639          if (!kobj->state_initialized)
   640              WARN(1, KERN_WARNING
   641                  "kobject: '%s' (%p): is not initialized, "
   641                  "yet kobject_get() is being called.\n",
   642               kobject_name(kobj), kobj);
-> 643          kref_get(&kobj->kref);   /* unconditional increment */
   644      }
   645      return kobj;
   646  }

kobject_get_unless_zero() (line 649) checks for zero first via
kref_get_unless_zero, but is not used here.


6. i2c_get_adapter -- drivers/i2c/i2c-core-base.c:2612

  2602  struct i2c_adapter *i2c_get_adapter(int nr)
  2603  {
  2604      struct i2c_adapter *adapter;
  2605
  2606      mutex_lock(&core_lock);
  2607      adapter = idr_find(&i2c_adapter_idr, nr);
  2608      if (!adapter)
  2609          goto exit;
  2610
  2611      if (try_module_get(adapter->owner))
->2612          get_device(&adapter->dev);   /* crash: dev refcount == 0 */
  2613      else
  2614          adapter = NULL;
  2615
  2616   exit:
  2617      mutex_unlock(&core_lock);
  2618      return adapter;
  2619  }


7. i2cdev_open -- drivers/i2c/i2c-dev.c:603

   597  static int i2cdev_open(struct inode *inode, struct file *file)
   598  {
   599      unsigned int minor = iminor(inode);
   600      struct i2c_client *client;
   601      struct i2c_adapter *adap;
   602
-> 603      adap = i2c_get_adapter(minor);
   604      if (!adap)
   605          return -ENODEV;
   ...
   618  }


Tentative Analysis

The crash is a "refcount_t: addition on 0; use-after-free" WARNING in
refcount_warn_saturate(), triggered when get_device() is called on an
I2C adapter whose struct device refcount has already reached zero.

The sequence of events:

1. A task opens /dev/i2c-N, which calls i2cdev_open(), which calls
   i2c_get_adapter(minor).

2. i2c_get_adapter() takes core_lock, calls idr_find(), and finds the
   adapter in the IDR (non-NULL).  try_module_get() succeeds.
   get_device(&adapter->dev) is then called.

3. Concurrently, i2c_del_adapter() has already called
   device_unregister(&adap->dev), which drops the device refcount to
   zero.  At this point the adapter is still in the IDR: idr_remove()
   is not called until after wait_for_completion() returns.

4. There is therefore a window where i2c_get_adapter() can find the
   adapter in the IDR and attempt get_device() on an object whose
   kobject refcount is already 0, triggering the WARNING.

The race exists because i2c_del_adapter() removes the adapter from
the IDR only *after* wait_for_completion(), which itself is called
after device_unregister() (which drops the device refcount to zero
when there is only one reference).  i2c_get_adapter() holds core_lock
for its entire body including the get_device() call, but
i2c_del_adapter() releases core_lock before calling
device_unregister(), so the two can execute concurrently.

The bug was introduced by commit 611e12ea0f12 ("i2c: core: manage i2c
bus device refcount in i2c_[get|put]_adapter", 2015), which added
get_device() to i2c_get_adapter() without reordering the idr_remove()
call in i2c_del_adapter().  Before that commit only try_module_get()
was called, so the idr_remove() ordering was inconsequential.


Potential Solution

Move the idr_remove() call in i2c_del_adapter() to before the
device_unregister() call, still under core_lock.  Once the adapter is
removed from the IDR, any concurrent i2c_get_adapter() call will
receive NULL from idr_find() and return -ENODEV.  Callers that already
obtained a device reference before the idr_remove() hold a legitimate
reference; wait_for_completion() will correctly wait for them to
release it via i2c_put_adapter().

Rough patch (against e753c16cb3dd):

  --- a/drivers/i2c/i2c-core-base.c
  +++ b/drivers/i2c/i2c-core-base.c
  @@ i2c_del_adapter
  +    /* Remove from IDR before device_unregister() to prevent a
  +     * concurrent i2c_get_adapter() from calling 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);
  +
       /* wait until all references to the device are gone ... */
       init_completion(&adap->dev_released);
       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);
  -


More information

Oops-Analysis: http://oops.fenrus.org/reports/lkml/69e93024.a00a0220.17a17.0031.GAE_google.com/
Assisted-by: GitHub Copilot linux-kernel-oops-x86.

      reply	other threads:[~2026-04-25 21:44 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-22 20:31 [syzbot] [i2c?] WARNING: refcount bug in i2c_get_adapter (2) syzbot
2026-04-25 21:45 ` Arjan van de Ven [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260425214530.424398-1-arjan@linux.intel.com \
    --to=arjan@linux.intel.com \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=syzbot+c0291c8c9aaa473c7721@syzkaller.appspotmail.com \
    --cc=syzkaller-bugs@googlegroups.com \
    --cc=wsa+renesas@sang-engineering.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.