public inbox for linux-i2c@vger.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox