* [PATCH] driver core: complete deferred binds when drivers_autoprobe is off
@ 2026-10-08 15:41 mosafer
2026-10-08 15:53 ` sashiko-bot
2026-10-09 8:52 ` [PATCH] " Danilo Krummrich
0 siblings, 2 replies; 9+ messages in thread
From: mosafer @ 2026-10-08 15:41 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: driver-core, linux-usb, linux-kernel, syzbot+863936f50214e843ae0c,
Mohammad Mosafer, stable
From: Mohammad Mosafer <mohsafer@gmail.com>
device_add() registers a device and then runs the bus's initial probe
via bus_probe_device() -> device_initial_probe(). With
drivers_autoprobe disabled for the bus, device_initial_probe() skips
__device_attach() entirely.
That is correct for automatic *matching* against the bus's driver list,
but __device_attach() also doubles as "complete a bind that a driver
already initiated": when dev->driver has been pre-assigned outside the
normal match/probe path, its else-if branch finishes the bind by
calling device_bind_driver().
With autoprobe off, that second duty is skipped too. USB depends on
it: usb_driver_claim_interface() pre-sets dev->driver on an interface
that is not yet registered, documenting "let the future device_add()
bind it, bypassing probe()". Composite drivers (cdc-acm, cdc_ncm,
cdc_mbim, ...) rely on it to bind their sibling data interfaces. If
drivers_autoprobe is written with 0 while such an interface is
between the claim and its device_add(), the interface ends up
registered, with dev->driver set and iface->condition ==
USB_INTERFACE_BOUND, but never bound: its knode_driver is never
attached to the driver's klist_devices.
Teardown then trusts those flags: usb_driver_release_interface() ->
device_release_driver() -> __device_release_driver() runs a full
release and calls klist_remove(&dev->p->knode_driver) on a
never-attached node whose knode_klist() is NULL, which klist_put()
dereferences:
Oops: general protection fault
KASAN: null-ptr-deref in range [0x0000000000000058-0x000000000000005f]
klist_put <- klist_remove <- device_release_driver_internal <-
usb_driver_release_interface <- acm_disconnect / cdc_ncm_unbind
Reproduced with syzkaller's C reproducer on a 7.3.0-rc6 tree:
instrumentation shows the doomed interface's device_add() racing an
autoprobe=0 write; the bind-completion branch never runs for it; later
teardown hits klist_remove() with a pristine node. With this patch the
same instrumented race completes the bind inside the window (knode
attached, crash gone): 3 VMs, 5 min per run, race window hit and bind
completed 8 times, zero KLIST-REMOVE-BAD, zero Oops.
Gate the flag on what it means - automatic matching - not on the
completion of a bind the driver already requested: pass through to
__device_attach() when dev->driver is pre-set. Other subsystems that
preset dev->driver before device_add() (w1, tegra xusb,
zynqmp-ipi-mailbox) get the same correctness back; when dev->driver is
NULL the behavior is byte-for-byte unchanged.
Reported-by: syzbot+863936f50214e843ae0c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=863936f50214e843ae0c
Fixes: b8c5cec23d5c ("Driver core: udev triggered device-<>driver binding")
Cc: stable@vger.kernel.org
Signed-off-by: Mohammad Mosafer <mohsafer@gmail.com>
---
drivers/base/dd.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index f6525a7ee8c5..98315264295e 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -1149,7 +1149,17 @@ void device_initial_probe(struct device *dev)
if (!sp)
return;
- if (sp->drivers_autoprobe)
+ /*
+ * The drivers_autoprobe flag only suppresses matching the device
+ * against the bus drivers. If a driver has already been assigned
+ * to the device (e.g. usb_driver_claim_interface() pre-setting
+ * dev->driver for an unregistered interface, expecting device_add()
+ * to complete the bind), the bind must be finished regardless of
+ * autoprobe: otherwise the device ends up registered with
+ * dev->driver set but never bound, and teardown oopses removing
+ * the never-attached knode_driver from the driver klist.
+ */
+ if (sp->drivers_autoprobe || dev->driver)
__device_attach(dev, true);
subsys_put(sp);
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-08 15:41 [PATCH] driver core: complete deferred binds when drivers_autoprobe is off mosafer
@ 2026-10-08 15:53 ` sashiko-bot
2026-10-08 21:33 ` [PATCH v2] " mosafer
2026-10-09 8:52 ` [PATCH] " Danilo Krummrich
1 sibling, 1 reply; 9+ messages in thread
From: sashiko-bot @ 2026-10-08 15:53 UTC (permalink / raw)
To: mosafer; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] driver core: TOCTOU race in device_initial_probe() causes spurious autoprobe
--
commit 363268c1e7ac2e5ecc70b8f7c4762aa363c48d73
Author: Mohammad Mosafer <mohsafer@gmail.com>
driver core: complete deferred binds when drivers_autoprobe is off
This commit updates device_initial_probe() to complete deferred device binds
even when bus autoprobing is disabled. It conditionally calls
__device_attach() if dev->driver is already set, preventing a null-pointer
dereference during device teardown.
> diff --git a/drivers/base/dd.c b/drivers/base/dd.c
> index f6525a7ee8c54..98315264295e4 100644
> --- a/drivers/base/dd.c
> +++ b/drivers/base/dd.c
> @@ -1149,7 +1149,17 @@ void device_initial_probe(struct device *dev)
> if (!sp)
> return;
>
> - if (sp->drivers_autoprobe)
> + /*
> + * The drivers_autoprobe flag only suppresses matching the device
> + * against the bus drivers. If a driver has already been assigned
> + * to the device (e.g. usb_driver_claim_interface() pre-setting
> + * dev->driver for an unregistered interface, expecting device_add()
> + * to complete the bind), the bind must be finished regardless of
> + * autoprobe: otherwise the device ends up registered with
> + * dev->driver set but never bound, and teardown oopses removing
> + * the never-attached knode_driver from the driver klist.
> + */
> + if (sp->drivers_autoprobe || dev->driver)
> __device_attach(dev, true);
[Severity: Medium]
If sp->drivers_autoprobe is 0 but dev->driver is set, device_initial_probe()
locklessly evaluates this as true and calls __device_attach().
Before __device_attach() acquires device_lock(dev), what happens if a
concurrent operation (like a sysfs unbind or sibling device disconnect)
acquires the lock, detaches the driver, and sets dev->driver to NULL?
When __device_attach() subsequently acquires the lock, it would check
dev->driver. Finding it NULL, it would skip the pre-assigned bind logic and
fall through to the else block:
static int __device_attach(struct device *dev, bool allow_async)
{
...
device_lock(dev);
if (dev->p->dead) {
goto out_unlock;
} else if (dev->driver) {
...
} else {
...
ret = bus_for_each_drv(dev->bus, NULL, &data,
__device_attach_driver);
This else block unconditionally executes bus_for_each_drv(), which performs
a full automatic match and probe against all drivers on the bus.
Does this result in a spurious bus-wide autoprobe, directly violating the
administrator's explicit drivers_autoprobe = 0 policy?
>
> subsys_put(sp);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008154105.256340-1-mosafer@node0.quickhttpnode15.cloudfaas-pg0.wisc.cloudlab.us?part=1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-08 15:53 ` sashiko-bot
@ 2026-10-08 21:33 ` mosafer
2026-10-08 21:39 ` sashiko-bot
0 siblings, 1 reply; 9+ messages in thread
From: mosafer @ 2026-10-08 21:33 UTC (permalink / raw)
To: sashiko-bot
Cc: linux-usb, mohsafer, sashiko-reviews, syzbot+863936f50214e843ae0c,
stable
From: Mohammad Mosafer <mohsafer@gmail.com>
device_add() registers a device and then runs the bus's initial probe
via bus_probe_device() -> device_initial_probe(). With
drivers_autoprobe disabled for the bus, device_initial_probe() skipped
__device_attach() entirely.
That is correct for automatic *matching* against the bus's driver list,
but __device_attach() also doubles as "complete a bind that a driver
already initiated": when dev->driver has been pre-assigned outside the
normal match/probe path, it finishes the bind by calling
device_bind_driver() under the device lock, bypassing probe().
With autoprobe off, that second duty was skipped too. USB depends on
it: usb_driver_claim_interface() pre-sets dev->driver on an interface
that is not yet registered, documenting "let the future device_add()
bind it, bypassing probe()". Composite drivers (cdc-acm, cdc_ncm,
cdc_mbim, ...) rely on it to bind their sibling data interfaces. If
drivers_autoprobe is written with 0 while such an interface is between
the claim and its device_add(), the interface ends up registered, with
dev->driver set and iface->condition == USB_INTERFACE_BOUND, but never
bound: its knode_driver is never attached to the driver's
klist_devices.
Teardown then trusts those flags: usb_driver_release_interface() ->
device_release_driver() -> __device_release_driver() runs a full
release and calls klist_remove(&dev->p->knode_driver) on a
never-attached node whose knode_klist() is NULL, which klist_put()
dereferences:
Oops: general protection fault
KASAN: null-ptr-deref in range [0x0000000000000058-0x000000000000005f]
klist_put <- klist_remove <- device_release_driver_internal <-
usb_driver_release_interface <- acm_disconnect / cdc_ncm_unbind
Keep device_initial_probe() always calling __device_attach(), but pass
whether automatic matching is allowed (sp->drivers_autoprobe) down to
it, and evaluate that flag inside __device_attach() under the device
lock, where dev->driver is read anyway: a device with a pre-assigned
driver completes its bind regardless of autoprobe, and only a device
without one is subject to the matching policy. Evaluating the two
conditions inside the locked branch (rather than as
"autoprobe || dev->driver" in the caller) also closes a TOCTOU: a
concurrent detach can no longer turn the completion of a pre-assigned
bind into bus-wide matching behind an "autoprobe off" policy.
Other subsystems that preset dev->driver before device_add() (w1,
tegra xusb, zynqmp-ipi-mailbox) get the same correctness back; for
devices with no pre-assigned driver and autoprobe off, behavior is
unchanged.
Reproduced with syzkaller's C reproducer on a 7.3.0-rc6 tree:
instrumentation shows the doomed interface's device_add() racing an
autoprobe=0 write; the bind-completion branch never runs for it; later
teardown hits klist_remove() with a pristine node. With this patch the
same instrumented race completes the bind inside the window (3 VMs,
5 min per run: window hit 3 times, bind completed 3 times, and 802
devices with no pre-assigned driver correctly skipped matching despite
__device_attach() now always running): zero KLIST-REMOVE-BAD, zero
Oops.
Reported-by: syzbot+863936f50214e843ae0c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=863936f50214e843ae0c
Fixes: b8c5cec23d5c ("Driver core: udev triggered device-<>driver binding")
Cc: stable@vger.kernel.org
Signed-off-by: Mohammad Mosafer <mohsafer@gmail.com>
---
---
Thanks for the review. The TOCTOU is real: v1 evaluated
"sp->drivers_autoprobe || dev->driver" in device_initial_probe() without
device_lock held, so a concurrent detach in that window could make
__device_attach() observe dev->driver == NULL under the lock and fall
through to bus_for_each_drv() - a bus-wide match despite the
drivers_autoprobe=0 policy.
Changes since v1:
- Move the decision into __device_attach(): it gains an allow_match
argument (device_initial_probe() passes sp->drivers_autoprobe,
device_attach() passes true) and evaluates it inside the locked
if/else chain: a pre-assigned dev->driver completes its bind
regardless of autoprobe; the bus_for_each_drv() matching branch only
runs when matching is allowed or requested. dev->driver is now read
exactly once, under device_lock, so a concurrent detach can neither
bypass the bind completion nor turn it into a spurious autoprobe.
- The crash fix itself is untouched code: the else-if branch that
completes the pre-assigned bind is identical to v1 / to the
autoprobe-on path; only its reachability changed.
Re-verified with the same instrumented race (3 VMs, 5 min): window hit
3 times (DATT match=0 drv=1 bound=0), bind completed all 3 (DD-BIND,
teardown attached=1), and 802 devices with no pre-assigned driver under
match=0 returned without touching bus_for_each_drv(); zero
KLIST-REMOVE-BAD, zero Oops.
v1: https://lore.kernel.org/all/20261008154105.256340-1-mosafer@node0.quickhttpnode15.cloudfaas-pg0.wisc.cloudlab.us/
review: https://lore.kernel.org/all/sashiko-outbox-164361@kernel.org/
drivers/base/dd.c | 30 ++++++++++++++++++++++++++----
1 file changed, 26 insertions(+), 4 deletions(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index f6525a7ee8c5..4c1bb1979519 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -1065,7 +1065,8 @@ static void __device_attach_async_helper(void *_dev, async_cookie_t cookie)
put_device(dev);
}
-static int __device_attach(struct device *dev, bool allow_async)
+static int __device_attach(struct device *dev, bool allow_async,
+ bool allow_match)
{
int ret = 0;
bool async = false;
@@ -1085,6 +1086,17 @@ static int __device_attach(struct device *dev, bool allow_async)
device_set_driver(dev, NULL);
ret = 0;
}
+ } else if (!allow_match) {
+ /*
+ * Automatic driver matching is suppressed for this device
+ * (drivers_autoprobe is off), and no driver has been
+ * assigned to it: do not run bus_for_each_drv() matching
+ * for it. Deciding this here, under device_lock, means a
+ * concurrent driver detach between the caller picking
+ * allow_match=false and reaching here cannot turn the
+ * completion of a pre-assigned bind into bus-wide matching.
+ */
+ goto out_unlock;
} else {
struct device_attach_data data = {
.dev = dev,
@@ -1138,7 +1150,7 @@ static int __device_attach(struct device *dev, bool allow_async)
*/
int device_attach(struct device *dev)
{
- return __device_attach(dev, false);
+ return __device_attach(dev, false, true);
}
EXPORT_SYMBOL_GPL(device_attach);
@@ -1149,8 +1161,18 @@ void device_initial_probe(struct device *dev)
if (!sp)
return;
- if (sp->drivers_autoprobe)
- __device_attach(dev, true);
+ /*
+ * Always run the attach, but tell it whether automatic matching is
+ * allowed. Completing a bind that a driver already initiated (e.g.
+ * usb_driver_claim_interface() pre-setting dev->driver for an
+ * unregistered interface, expecting device_add() to bind it) is not
+ * automatic matching and must happen even with drivers_autoprobe
+ * off; matching is evaluated in __device_attach() under the device
+ * lock. Skipping the call entirely used to leave such devices
+ * registered-but-unbound, and teardown then oopsed klist_removing
+ * the never-attached knode_driver.
+ */
+ __device_attach(dev, true, sp->drivers_autoprobe);
subsys_put(sp);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-08 21:33 ` [PATCH v2] " mosafer
@ 2026-10-08 21:39 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-08 21:39 UTC (permalink / raw)
To: mosafer; +Cc: linux-usb
> From: Mohammad Mosafer <mohsafer@gmail.com>
>
> device_add() registers a device and then runs the bus's initial probe
> via bus_probe_device() -> device_initial_probe(). With
> drivers_autoprobe disabled for the bus, device_initial_probe() skipped
> __device_attach() entirely.
>
> That is correct for automatic *matching* against the bus's driver list,
> but __device_attach() also doubles as "complete a bind that a driver
> already initiated": when dev->driver has been pre-assigned outside the
> normal match/probe path, it finishes the bind by calling
> device_bind_driver() under the device lock, bypassing probe().
>
> With autoprobe off, that second duty was skipped too. USB depends on
> it: usb_driver_claim_interface() pre-sets dev->driver on an interface
> [ ... ]
> Reported-by: syzbot+863936f50214e843ae0c@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=863936f50214e843ae0c
> Fixes: b8c5cec23d5c ("Driver core: udev triggered device-<>driver binding")
> Cc: stable@vger.kernel.org
> Signed-off-by: Mohammad Mosafer <mohsafer@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008213320.268141-1-mosafer@node0.quickhttpnode15.cloudfaas-pg0.wisc.cloudlab.us?part=1
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-08 15:41 [PATCH] driver core: complete deferred binds when drivers_autoprobe is off mosafer
2026-10-08 15:53 ` sashiko-bot
@ 2026-10-09 8:52 ` Danilo Krummrich
2026-10-09 18:22 ` [PATCH v2] " mosafer
1 sibling, 1 reply; 9+ messages in thread
From: Danilo Krummrich @ 2026-10-09 8:52 UTC (permalink / raw)
To: mosafer
Cc: Greg Kroah-Hartman, Rafael J. Wysocki, driver-core, linux-usb,
linux-kernel, syzbot+863936f50214e843ae0c, stable
On Thu Oct 8, 2026 at 5:41 PM CEST, mosafer wrote:
> From: Mohammad Mosafer <mohsafer@gmail.com>
>
> device_add() registers a device and then runs the bus's initial probe
> via bus_probe_device() -> device_initial_probe(). With
> drivers_autoprobe disabled for the bus, device_initial_probe() skips
> __device_attach() entirely.
>
> That is correct for automatic *matching* against the bus's driver list,
> but __device_attach() also doubles as "complete a bind that a driver
> already initiated": when dev->driver has been pre-assigned outside the
> normal match/probe path, its else-if branch finishes the bind by
> calling device_bind_driver().
We've recently had another patch to fix up cases where dev->driver is
pre-assigned. Please see [1] and the corresponding thread.
IIRC, there was no reasons the remaining users can't just use a proper match()
callback instead, so we don't have to worry about those edge cases in the
future.
[1] https://lore.kernel.org/driver-core/20260820084557.129908-1-khiemtranzo532001@gmail.com/
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-09 8:52 ` [PATCH] " Danilo Krummrich
@ 2026-10-09 18:22 ` mosafer
2026-10-09 18:28 ` sashiko-bot
2026-10-10 1:49 ` Alan Stern
0 siblings, 2 replies; 9+ messages in thread
From: mosafer @ 2026-10-09 18:22 UTC (permalink / raw)
To: dakr
Cc: driver-core, gregkh, linux-kernel, linux-usb, mohsafer, rafael,
stable, syzbot+863936f50214e843ae0c, khiemtranzo532001
From: Mohammad Mosafer <mohsafer@gmail.com>
device_add() registers a device and then runs the bus's initial probe
via bus_probe_device() -> device_initial_probe(). With
drivers_autoprobe disabled for the bus, device_initial_probe() skipped
__device_attach() entirely.
That is correct for automatic *matching* against the bus's driver list,
but __device_attach() also doubles as "complete a bind that a driver
already initiated": when dev->driver has been pre-assigned outside the
normal match/probe path, it finishes the bind by calling
device_bind_driver() under the device lock, bypassing probe().
With autoprobe off, that second duty was skipped too. USB depends on
it: usb_driver_claim_interface() pre-sets dev->driver on an interface
that is not yet registered, documenting "let the future device_add()
bind it, bypassing probe()". Composite drivers (cdc-acm, cdc_ncm,
cdc_mbim, ...) rely on it to bind their sibling data interfaces. If
drivers_autoprobe is written with 0 while such an interface is between
the claim and its device_add(), the interface ends up registered, with
dev->driver set and iface->condition == USB_INTERFACE_BOUND, but never
bound: its knode_driver is never attached to the driver's
klist_devices.
Teardown then trusts those flags: usb_driver_release_interface() ->
device_release_driver() -> __device_release_driver() runs a full
release and calls klist_remove(&dev->p->knode_driver) on a
never-attached node whose knode_klist() is NULL, which klist_put()
dereferences:
Oops: general protection fault
KASAN: null-ptr-deref in range [0x0000000000000058-0x000000000000005f]
klist_put <- klist_remove <- device_release_driver_internal <-
usb_driver_release_interface <- acm_disconnect / cdc_ncm_unbind
Keep device_initial_probe() always calling __device_attach(), but pass
whether automatic matching is allowed (sp->drivers_autoprobe) down to
it, and evaluate that flag inside __device_attach() under the device
lock, where dev->driver is read anyway: a device with a pre-assigned
driver completes its bind regardless of autoprobe, and only a device
without one is subject to the matching policy. Evaluating the two
conditions inside the locked branch (rather than as
"autoprobe || dev->driver" in the caller) also closes a TOCTOU: a
concurrent detach can no longer turn the completion of a pre-assigned
bind into bus-wide matching behind an "autoprobe off" policy.
Other subsystems that preset dev->driver before device_add() (w1,
tegra xusb, zynqmp-ipi-mailbox) get the same correctness back; for
devices with no pre-assigned driver and autoprobe off, behavior is
unchanged.
Reproduced with syzkaller's C reproducer on a 7.3.0-rc6 tree:
instrumentation shows the doomed interface's device_add() racing an
autoprobe=0 write; the bind-completion branch never runs for it; later
teardown hits klist_remove() with a pristine node. With this patch the
same instrumented race completes the bind inside the window (3 VMs,
5 min per run: window hit 3 times, bind completed 3 times, and 802
devices with no pre-assigned driver correctly skipped matching despite
__device_attach() now always running): zero KLIST-REMOVE-BAD, zero
Oops.
Reported-by: syzbot+863936f50214e843ae0c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=863936f50214e843ae0c
Fixes: b8c5cec23d5c ("Driver core: udev triggered device-<>driver binding")
Cc: stable@vger.kernel.org
Signed-off-by: Mohammad Mosafer <mohsafer@gmail.com>
---
On Fri Oct 9, Danilo Krummrich wrote:
> We've recently had another patch to fix up cases where dev->driver is
> pre-assigned. Please see [1] and the corresponding thread.
>
> IIRC, there was no reasons the remaining users can't just use a proper
> match() callback instead, so we don't have to worry about those edge
> cases in the future.
Thanks for the pointer.
I had not seen Kien's series; looking at it now, we are attacking the
same half-claimed state from opposite ends: his patch makes the release
path tolerant of it (skip klist_remove() when the device is not bound),
while mine makes the deferred bind that usb_driver_claim_interface()
promises actually complete when drivers_autoprobe is off, so the
half-claimed state never arises. If the direction is to remove the
pre-assigned dev->driver users entirely via proper match() callbacks,
I'm happy for that to supersede this fix -- I'd be glad to help with
the USB side of that conversion, as I now know the claim/bind/teardown
paths in detail.
Until such a conversion lands, though, the window is still open in
current releases (syzbot hits it, and any userspace writing
drivers_autoprobe races it), and pre-assignment is not only USB:
drivers/w1/, drivers/phy/tegra/xusb.c and
drivers/mailbox/zynqmp-ipi-mailbox.c pre-assign dev->driver the same
way today.
So please treat this as: whichever of Kien's guard, this bind-side
fix, or the match() conversion you want as the canonical answer, this
patch is yours to drop or pick -- but happy to have your steer before
it evolves further.
While the v1 was on the list the review bot found a real defect (the
dev->driver test was open-coded in device_initial_probe() lockless, a
TOCTOU against a concurrent detach). v2 below fixes it the way the
bot suggested: __device_attach() takes an allow_match flag and the
decision is evaluated under device_lock. Evidence unchanged in kind:
same reproducer, instrumented patched boots show the deferred bind
completing in the previously-crashing window, 0 crashes (v1: 6/9 on
the unpatched control), plus a clean bare soak.
drivers/base/dd.c | 30 ++++++++++++++++++++++++++----
1 file changed, 26 insertions(+), 4 deletions(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index f6525a7ee8c5..4c1bb1979519 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -1065,7 +1065,8 @@ static void __device_attach_async_helper(void *_dev, async_cookie_t cookie)
put_device(dev);
}
-static int __device_attach(struct device *dev, bool allow_async)
+static int __device_attach(struct device *dev, bool allow_async,
+ bool allow_match)
{
int ret = 0;
bool async = false;
@@ -1085,6 +1086,17 @@ static int __device_attach(struct device *dev, bool allow_async)
device_set_driver(dev, NULL);
ret = 0;
}
+ } else if (!allow_match) {
+ /*
+ * Automatic driver matching is suppressed for this device
+ * (drivers_autoprobe is off), and no driver has been
+ * assigned to it: do not run bus_for_each_drv() matching
+ * for it. Deciding this here, under device_lock, means a
+ * concurrent driver detach between the caller picking
+ * allow_match=false and reaching here cannot turn the
+ * completion of a pre-assigned bind into bus-wide matching.
+ */
+ goto out_unlock;
} else {
struct device_attach_data data = {
.dev = dev,
@@ -1138,7 +1150,7 @@ static int __device_attach(struct device *dev, bool allow_async)
*/
int device_attach(struct device *dev)
{
- return __device_attach(dev, false);
+ return __device_attach(dev, false, true);
}
EXPORT_SYMBOL_GPL(device_attach);
@@ -1149,8 +1161,18 @@ void device_initial_probe(struct device *dev)
if (!sp)
return;
- if (sp->drivers_autoprobe)
- __device_attach(dev, true);
+ /*
+ * Always run the attach, but tell it whether automatic matching is
+ * allowed. Completing a bind that a driver already initiated (e.g.
+ * usb_driver_claim_interface() pre-setting dev->driver for an
+ * unregistered interface, expecting device_add() to bind it) is not
+ * automatic matching and must happen even with drivers_autoprobe
+ * off; matching is evaluated in __device_attach() under the device
+ * lock. Skipping the call entirely used to leave such devices
+ * registered-but-unbound, and teardown then oopsed klist_removing
+ * the never-attached knode_driver.
+ */
+ __device_attach(dev, true, sp->drivers_autoprobe);
subsys_put(sp);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-09 18:22 ` [PATCH v2] " mosafer
@ 2026-10-09 18:28 ` sashiko-bot
2026-10-10 1:49 ` Alan Stern
1 sibling, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-09 18:28 UTC (permalink / raw)
To: mosafer; +Cc: linux-usb
> From: Mohammad Mosafer <mohsafer@gmail.com>
>
> device_add() registers a device and then runs the bus's initial probe
> via bus_probe_device() -> device_initial_probe(). With
> drivers_autoprobe disabled for the bus, device_initial_probe() skipped
> __device_attach() entirely.
>
> That is correct for automatic *matching* against the bus's driver list,
> but __device_attach() also doubles as "complete a bind that a driver
> already initiated": when dev->driver has been pre-assigned outside the
> normal match/probe path, it finishes the bind by calling
> device_bind_driver() under the device lock, bypassing probe().
>
> With autoprobe off, that second duty was skipped too. USB depends on
> it: usb_driver_claim_interface() pre-sets dev->driver on an interface
> [ ... ]
> Reported-by: syzbot+863936f50214e843ae0c@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=863936f50214e843ae0c
> Fixes: b8c5cec23d5c ("Driver core: udev triggered device-<>driver binding")
> Cc: stable@vger.kernel.org
> Signed-off-by: Mohammad Mosafer <mohsafer@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261009182239.283329-1-mosafer@node0.quickhttpnode15.cloudfaas-pg0.wisc.cloudlab.us?part=1
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-09 18:22 ` [PATCH v2] " mosafer
2026-10-09 18:28 ` sashiko-bot
@ 2026-10-10 1:49 ` Alan Stern
2026-10-10 7:00 ` mosafer
1 sibling, 1 reply; 9+ messages in thread
From: Alan Stern @ 2026-10-10 1:49 UTC (permalink / raw)
To: mosafer
Cc: dakr, driver-core, gregkh, linux-kernel, linux-usb, rafael,
stable, syzbot+863936f50214e843ae0c, khiemtranzo532001
On Fri, Oct 09, 2026 at 01:22:39PM -0500, mosafer wrote:
> On Fri Oct 9, Danilo Krummrich wrote:
> > We've recently had another patch to fix up cases where dev->driver is
> > pre-assigned. Please see [1] and the corresponding thread.
> >
> > IIRC, there was no reasons the remaining users can't just use a proper
> > match() callback instead, so we don't have to worry about those edge
> > cases in the future.
>
> Thanks for the pointer.
>
> I had not seen Kien's series; looking at it now, we are attacking the
> same half-claimed state from opposite ends: his patch makes the release
> path tolerant of it (skip klist_remove() when the device is not bound),
> while mine makes the deferred bind that usb_driver_claim_interface()
> promises actually complete when drivers_autoprobe is off, so the
> half-claimed state never arises. If the direction is to remove the
> pre-assigned dev->driver users entirely via proper match() callbacks,
> I'm happy for that to supersede this fix -- I'd be glad to help with
> the USB side of that conversion, as I now know the claim/bind/teardown
> paths in detail.
>
> Until such a conversion lands, though, the window is still open in
> current releases (syzbot hits it, and any userspace writing
> drivers_autoprobe races it), and pre-assignment is not only USB:
> drivers/w1/, drivers/phy/tegra/xusb.c and
> drivers/mailbox/zynqmp-ipi-mailbox.c pre-assign dev->driver the same
> way today.
>
> So please treat this as: whichever of Kien's guard, this bind-side
> fix, or the match() conversion you want as the canonical answer, this
> patch is yours to drop or pick -- but happy to have your steer before
> it evolves further.
IMO, we do not want the system to limp along forever with the device in
this "half-bound" state. Claiming an interface should cause the driver
core to create a real binding at some point in the near future.
This should happen even without automatic probing. For example, if both
interfaces are unbound and the user writes one of them to the driver's
"bind" sysfs attribute, both interfaces should end up bound to the
driver after only a short delay.
Alan Stern
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] driver core: complete deferred binds when drivers_autoprobe is off
2026-10-10 1:49 ` Alan Stern
@ 2026-10-10 7:00 ` mosafer
0 siblings, 0 replies; 9+ messages in thread
From: mosafer @ 2026-10-10 7:00 UTC (permalink / raw)
To: stern
Cc: dakr, driver-core, gregkh, khiemtranzo532001, linux-kernel,
linux-usb, mohsafer, rafael, stable, syzbot+863936f50214e843ae0c
On Fri, Oct 09, 2026 at 09:49:49PM -0400, Alan Stern wrote:
> IMO, we do not want the system to limp along forever with the device in
> this "half-bound" state. Claiming an interface should cause the driver
> core to create a real binding at some point in the near future.
>
> This should happen even without automatic probing. For example, if both
> interfaces are unbound and the user writes one of them to the driver's
> "bind" sysfs attribute, both interfaces should end up bound to the
> driver after only a short delay.
Agreed --- that invariant is exactly what this patch restores.
drivers_autoprobe is meant to suppress automatic *matching* against the
bus's driver list; it should not cancel a bind that a driver already
initiated by claiming the interface. With the patch,
usb_driver_claim_interface()'s promise ("let the future device_add()
bind it") is kept at device_add() time regardless of the knob: the
deferred bind completes through device_bind_driver() under the device
lock, so the interface ends up really bound, not half-bound.
To your example: when the claimed interface is already registered,
usb_driver_claim_interface() binds it immediately --- it checks
device_is_registered() and calls device_bind_driver() inline --- so
the manual "bind" attribute case works today and is unaffected by this
patch. The one case where the invariant was broken, and the case the
patch fixes, is a claim made while the sibling interface is not yet
registered and the autoprobe knob happens to be off at its
device_add(): the core silently dropped the promised completion, and
the interface stayed registered with dev->driver set but never bound.
That state is beyond repair from userspace too: writing the interface
to the driver's "bind" attribute cannot complete it --- with
dev->driver pre-assigned, __driver_probe_device() returns -EBUSY (or
the attribute path rejects the device if it does not match the
driver's id table), so interface teardown was the only way out --- and
it oopsed.
The patch keeps the knob's meaning for devices with no claiming
driver: bus-wide matching is still skipped when autoprobe is off (the
check now sits inside __device_attach(), evaluated under the device
lock, per the review-bot finding on v1). On the reproducer that means
the previously-crashing window now ends with a completed bind instead
of an oops, while matching stays suppressed for all the other devices
registered while the knob is off.
If you would rather the same invariant be implemented with a
different mechanism, say the word and I will respin.
Thanks for taking a look,
Mohammad Mosafer
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-10 7:00 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-08 15:41 [PATCH] driver core: complete deferred binds when drivers_autoprobe is off mosafer
2026-10-08 15:53 ` sashiko-bot
2026-10-08 21:33 ` [PATCH v2] " mosafer
2026-10-08 21:39 ` sashiko-bot
2026-10-09 8:52 ` [PATCH] " Danilo Krummrich
2026-10-09 18:22 ` [PATCH v2] " mosafer
2026-10-09 18:28 ` sashiko-bot
2026-10-10 1:49 ` Alan Stern
2026-10-10 7:00 ` mosafer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox