Re: [PATCH net] can: j1939: avoid address-claim timer deadlock

[email protected] Sat, 01 Aug 2026 13:43:15 +0000
Newsgroups org.kernel.vger.linux-can
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 4 potential issue(=
s) to consider:

Pre-existing issues:
- [High] Use-after-free on `priv->lock` during ECU timer completion.
- [High] Data race and unprotected `list_del()` causing list corruption on =
ECU release.
- [Medium] Memory leak of unmapped ECUs and `j1939_priv` due to missing tim=
er cancellation.
- [Medium] Logic Bug: Resurrecting lost address claim contention.
--

--- Patch [1]: [PATCH net] can: j1939: avoid address-claim timer deadlock -=
--
commit a5db6a778fcc4768a707910335bdf934adc29468
Author: Felix Hoffmann <[email protected]>

can: j1939: avoid address-claim timer deadlock

j1939_ac_process() holds priv->lock while synchronously canceling an
ECU's address-claim hrtimer. The timer callback takes the same lock. If the
callback starts on another CPU after the receive path takes the lock, the
callback waits for priv->lock while hrtimer_cancel() waits for the callback
to finish. This deadlocks both CPUs and makes the system unresponsive.

> diff --git a/net/can/j1939/bus.c b/net/can/j1939/bus.c
> index cdc3c0a719379..ac654dc8872e6 100644
> --- a/net/can/j1939/bus.c
> +++ b/net/can/j1939/bus.c
> @@ -131,7 +131,17 @@ static enum hrtimer_restart j1939_ecu_timer_handler(=
struct hrtimer *hrtimer)
>  		container_of(hrtimer, struct j1939_ecu, ac_timer);
>  	struct j1939_priv *priv =3D ecu->priv;
> =20
> -	write_lock_bh(&priv->lock);
> +	/*
> +	 * j1939_ac_process() cancels this timer while holding priv->lock.
> +	 * Don't block here, otherwise the timer and receive paths can deadlock

[Severity: Medium]
This isn't a bug introduced by this patch, but does j1939_ac_process() alwa=
ys
cancel the timer?

If a new address claim takes precedence over an existing claimant (prev),
prev is evicted via j1939_ecu_unmap_locked(prev). However, it appears its
pending ac_timer is not cancelled.

If the winning ECU vacates the address before prev's timer expires, could t=
he
address slot become free, allowing prev's timer to fire and blindly re-map
itself without broadcasting an Address Claim message?

> +	 * waiting for each other on different CPUs. Retry shortly if address
> +	 * claim processing currently owns the lock.
> +	 */
> +	if (!write_trylock(&priv->lock)) {
> +		hrtimer_forward_now(hrtimer, ms_to_ktime(1));
> +		return HRTIMER_RESTART;
> +	}
> +
>  	/* TODO: can we test if ecu->addr is unicast before starting
>  	 * the timer?
>  	 */
>  	j1939_ecu_map_locked(ecu);

[Severity: Medium]
This is a pre-existing issue, but can this lead to a memory leak when an
interface goes down?

When a network interface goes down, j1939_ecu_unmap_all() clears the ECU
entries from priv->ents. If it fails to cancel the pending ac_timer for the
ECUs, could this handler execute and call j1939_ecu_map_locked(ecu)?

Since the slot was cleared, the ECU would be successfully re-mapped and its
reference count incremented, potentially leaving it leaked along with the
j1939_priv object since the interface is already down.

> =20
>  	/* The corresponding j1939_ecu_get() is in
>  	 * j1939_ecu_timer_start().
>  	 */
>  	j1939_ecu_put(ecu);
> -	write_unlock_bh(&priv->lock);
> +	write_unlock(&priv->lock);

[Severity: High]
This isn't a bug introduced by this patch, but could this write_unlock()
trigger a use-after-free?

If the timer holds the last reference to the ECU, j1939_ecu_put(ecu) drops =
its
refcount to 0, invoking __j1939_ecu_release(). This in turn calls
j1939_priv_put(priv).

If this drops the final reference to the j1939_priv structure (e.g., after a
network interface teardown dropped the base reference), priv is synchronous=
ly
freed via kfree(). Immediately after j1939_ecu_put() returns, the handler
dereferences the freed memory by calling write_unlock(&priv->lock).

[Severity: High]
This is also a pre-existing issue, but is there a risk of list corruption
when j1939_ecu_put() drops the final reference without priv->lock being held
in other contexts?

While priv->lock is held here, j1939_ecu_put() is called from functions
like j1939_ac_recv() in address-claim.c without holding this lock.

If j1939_ecu_put() drops the final reference, __j1939_ecu_release() executes
list_del(&ecu->list), which assumes priv->lock is held for safe modification
of priv->ecus. Could concurrent operations trigger this and silently corrupt
the list?

> =20
>  	return HRTIMER_NORESTART;
>  }

--=20
Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/20260731134255.1002=
[email protected]?part=3D1