Linux USB
 help / color / mirror / Atom feed
* [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; 7+ 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] 7+ 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; 7+ 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] 7+ 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; 7+ 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] 7+ 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; 7+ 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] 7+ 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; 7+ 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] 7+ 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
  0 siblings, 1 reply; 7+ 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] 7+ 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
  0 siblings, 0 replies; 7+ 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] 7+ messages in thread

end of thread, other threads:[~2026-10-09 18:28 UTC | newest]

Thread overview: 7+ 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox