From: "syzbot" <syzbot@kernel.org>
To: syzkaller-upstream-moderation@googlegroups.com
Cc: syzbot@lists.linux.dev
Subject: [PATCH RFC] i2c: core: fix adapter deregistration race and USB PHY type overwrite
Date: Wed, 12 Aug 2026 05:24:19 +0000 (UTC) [thread overview]
Message-ID: <cfb582d8-dbab-4e13-a732-e113dfd66617@mail.kernel.org> (raw)
The USB PHY subsystem incorrectly overwrites the `type` of the device
passed to it in `usb_add_phy_dev()`. This destroys the device's original
`release` function (e.g., `i2c_client_dev_release`), leading to a memory
leak and a warning when the device is removed:
WARNING: drivers/base/core.c:2639 at device_release+0x194/0x1f0
Call Trace:
kobject_put+0x222/0x550 lib/kobject.c:737
i2c_deregister_clients+0x281/0x3a0 drivers/i2c/i2c-core-base.c:1782
i2c_del_adapter+0x10c/0x430 drivers/i2c/i2c-core-base.c:1820
i2c_tiny_usb_disconnect+0x49/0xd0 drivers/i2c/busses/i2c-tiny-usb.c:282
Additionally, there is a race condition between `new_device_store()` and
`i2c_del_adapter()`. If `new_device_store()` creates a new client device
while `i2c_del_adapter()` is deregistering clients, it can result in a
double unregister of the I2C client. There is also a race involving debugfs
removal during adapter deregistration, which triggers KASAN null-ptr-deref
errors:
KASAN: null-ptr-deref in range [0x0000000000000140-0x0000000000000147]
Call Trace:
__kasan_check_byte+0x12/0x40 mm/kasan/common.c:573
lock_acquire+0x84/0x350 kernel/locking/lockdep.c:5842
down_write_nested+0x9d/0x210 kernel/locking/rwsem.c:1757
start_dirop+0x4f/0x90 fs/namei.c:2942
debugfs_create_dir+0x24/0x350 fs/debugfs/inode.c:572
i2c_device_probe+0x814/0xbf0 drivers/i2c/i2c-core-base.c:588
To fix the USB PHY issue, we stop overwriting `dev->type` in
`usb_add_phy_dev()` and instead pass the uevent environment variables
directly in `usb_phy_notify_charger_work()`. We also add
`cancel_work_sync(&x->chg_work)` in `usb_remove_phy()` to ensure no pending
work accesses the freed PHY.
To fix the I2C race condition, we hold `adap->userspace_clients_lock` in
`new_device_store()` before checking the IDR and keep it held while calling
`i2c_new_client_device()`. This ensures that if `i2c_del_adapter()` is
running, it will either block waiting for the lock to finish and safely
unregister the newly created client, or it has already removed the adapter
from the IDR, causing `new_device_store()` to abort.
Finally, we move `debugfs_remove_recursive(adap->debugfs)` to the end of
`i2c_del_adapter()` to prevent use-after-free issues if debugfs files are
accessed during adapter removal.
Fixes: a8534cb092d7 ("usb: phy: introduce usb_phy device type with its own uevent handler")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: syzbot+3fb7629cfd12d04beeab@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=3fb7629cfd12d04beeab
Link: https://syzkaller.appspot.com/ai_job?id=63493d50-95d9-4a1c-9ccf-fa45ca05bef8
To: "Andi Shyti" <andi.shyti@kernel.org>
To: "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>
To: <linux-i2c@vger.kernel.org>
To: <linux-usb@vger.kernel.org>
To: "Grzegorz Jaszczyk" <grzegorz.jaszczyk@linaro.org>
Cc: "Diogo Ivo" <diogo.ivo@tecnico.ulisboa.pt>
Cc: <linux-kernel@vger.kernel.org>
---
diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index 3ec04787a..f4ff6ce5b 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -1304,12 +1304,24 @@ new_device_store(struct device *dev, struct device_attribute *attr,
info.flags |= I2C_CLIENT_SLAVE;
}
+ mutex_lock_nested(&adap->userspace_clients_lock,
+ i2c_adapter_depth(adap));
+
+ mutex_lock(&core_lock);
+ if (idr_find(&i2c_adapter_idr, adap->nr) != adap) {
+ mutex_unlock(&core_lock);
+ mutex_unlock(&adap->userspace_clients_lock);
+ return -ENODEV;
+ }
+ mutex_unlock(&core_lock);
+
client = i2c_new_client_device(adap, &info);
- if (IS_ERR(client))
+ if (IS_ERR(client)) {
+ mutex_unlock(&adap->userspace_clients_lock);
return PTR_ERR(client);
+ }
/* Keep track of the added device */
- mutex_lock(&adap->userspace_clients_lock);
list_add_tail(&client->detected, &adap->userspace_clients);
mutex_unlock(&adap->userspace_clients_lock);
dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
@@ -1826,8 +1838,6 @@ void i2c_del_adapter(struct i2c_adapter *adap)
i2c_host_notify_irq_teardown(adap);
- debugfs_remove_recursive(adap->debugfs);
-
/* wait until all references to the device are gone
*
* FIXME: This is old code and should ideally be replaced by an
@@ -1847,6 +1857,8 @@ void i2c_del_adapter(struct i2c_adapter *adap)
/* Clear the device structure in case this adapter is ever going to be
added again */
memset(&adap->dev, 0, sizeof(adap->dev));
+
+ debugfs_remove_recursive(adap->debugfs);
}
EXPORT_SYMBOL(i2c_del_adapter);
diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c
index 5a9b9353f..e5643d4ce 100644
--- a/drivers/usb/phy/phy.c
+++ b/drivers/usb/phy/phy.c
@@ -80,18 +80,6 @@ static struct usb_phy *__of_usb_find_phy(struct device_node *node)
return ERR_PTR(-EPROBE_DEFER);
}
-static struct usb_phy *__device_to_usb_phy(const struct device *dev)
-{
- struct usb_phy *usb_phy;
-
- list_for_each_entry(usb_phy, &phy_list, head) {
- if (usb_phy->dev == dev)
- return usb_phy;
- }
-
- return NULL;
-}
-
static void usb_phy_set_default_current(struct usb_phy *usb_phy)
{
usb_phy->chg_cur.sdp_min = DEFAULT_SDP_CUR_MIN;
@@ -123,6 +111,9 @@ static void usb_phy_set_default_current(struct usb_phy *usb_phy)
static void usb_phy_notify_charger_work(struct work_struct *work)
{
struct usb_phy *usb_phy = container_of(work, struct usb_phy, chg_work);
+ char uchger_state[50] = { 0 };
+ char uchger_type[50] = { 0 };
+ char *envp[3];
unsigned int min, max;
switch (usb_phy->chg_state) {
@@ -130,11 +121,15 @@ static void usb_phy_notify_charger_work(struct work_struct *work)
usb_phy_get_charger_current(usb_phy, &min, &max);
atomic_notifier_call_chain(&usb_phy->notifier, max, usb_phy);
+ snprintf(uchger_state, ARRAY_SIZE(uchger_state),
+ "USB_CHARGER_STATE=%s", usb_chger_state[usb_phy->chg_state]);
break;
case USB_CHARGER_ABSENT:
usb_phy_set_default_current(usb_phy);
atomic_notifier_call_chain(&usb_phy->notifier, 0, usb_phy);
+ snprintf(uchger_state, ARRAY_SIZE(uchger_state),
+ "USB_CHARGER_STATE=%s", usb_chger_state[usb_phy->chg_state]);
break;
default:
dev_warn(usb_phy->dev, "Unknown USB charger state: %d\n",
@@ -142,36 +137,12 @@ static void usb_phy_notify_charger_work(struct work_struct *work)
return;
}
- kobject_uevent(&usb_phy->dev->kobj, KOBJ_CHANGE);
-}
-
-static int usb_phy_uevent(const struct device *dev, struct kobj_uevent_env *env)
-{
- const struct usb_phy *usb_phy;
- char uchger_state[50] = { 0 };
- char uchger_type[50] = { 0 };
- unsigned long flags;
-
- spin_lock_irqsave(&phy_lock, flags);
- usb_phy = __device_to_usb_phy(dev);
- spin_unlock_irqrestore(&phy_lock, flags);
-
- if (!usb_phy)
- return -ENODEV;
-
- snprintf(uchger_state, ARRAY_SIZE(uchger_state),
- "USB_CHARGER_STATE=%s", usb_chger_state[usb_phy->chg_state]);
-
snprintf(uchger_type, ARRAY_SIZE(uchger_type),
"USB_CHARGER_TYPE=%s", usb_chger_type[usb_phy->chg_type]);
-
- if (add_uevent_var(env, uchger_state))
- return -ENOMEM;
-
- if (add_uevent_var(env, uchger_type))
- return -ENOMEM;
-
- return 0;
+ envp[0] = uchger_state;
+ envp[1] = uchger_type;
+ envp[2] = NULL;
+ kobject_uevent_env(&usb_phy->dev->kobj, KOBJ_CHANGE, envp);
}
static void __usb_phy_get_charger_type(struct usb_phy *usb_phy)
@@ -675,11 +646,6 @@ int usb_add_phy(struct usb_phy *x, enum usb_phy_type type)
}
EXPORT_SYMBOL_GPL(usb_add_phy);
-static const struct device_type usb_phy_dev_type = {
- .name = "usb_phy",
- .uevent = usb_phy_uevent,
-};
-
/**
* usb_add_phy_dev - declare the USB PHY
* @x: the USB phy to be used; or NULL
@@ -705,8 +671,6 @@ int usb_add_phy_dev(struct usb_phy *x)
if (ret)
return ret;
- x->dev->type = &usb_phy_dev_type;
-
ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
spin_lock_irqsave(&phy_lock, flags);
@@ -727,9 +691,13 @@ void usb_remove_phy(struct usb_phy *x)
{
unsigned long flags;
+ if (!x)
+ return;
+
+ cancel_work_sync(&x->chg_work);
+
spin_lock_irqsave(&phy_lock, flags);
- if (x)
- list_del(&x->head);
+ list_del(&x->head);
spin_unlock_irqrestore(&phy_lock, flags);
}
EXPORT_SYMBOL_GPL(usb_remove_phy);
base-commit: db2ddb87143519e20a95aa36c60b36107b736a58
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at syzkaller@googlegroups.com.
reply other threads:[~2026-08-12 5:24 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=cfb582d8-dbab-4e13-a732-e113dfd66617@mail.kernel.org \
--to=syzbot@kernel.org \
--cc=syzbot@lists.linux.dev \
--cc=syzkaller-upstream-moderation@googlegroups.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.