* [PATCH v5 0/4] net: can: isotp-fixes
@ 2026-07-10 16:47 Oliver Hartkopp via B4 Relay
2026-07-10 16:47 ` [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() Oliver Hartkopp via B4 Relay
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 UTC (permalink / raw)
To: linux-can; +Cc: Oliver Hartkopp, Marc Kleine-Budde, Nico Yip, sashiko-bot
As sashiko-bot was not able to check the second patch this bundle is
re-posted with b4 preparation.
Add another two patches with fixes reported by sashiko-bot.
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
---
Changes in v5:
- patch 3: new: state change re-check after hrtimer_cancel()
- patch 4: new: handle state transistion with cmpxchg()
- Link to v4: https://patch.msgid.link/20260710-isotp-fixes-v4-0-2a4af437f61b@hartkopp.net
Changes in v4:
- patch 2: use a simpler method to reject a re-binding that can be implemented
inside the lock: No waitqueue waiting just exit.
- Link to v3: https://patch.msgid.link/20260710-isotp-fixes-v3-0-08db68e27d0b@hartkopp.net
Changes in v3:
- patch 2: go back to initial idea without hard resetting the state machine and
timers but let these come to a graceful end. Even a new bind() at netdev
shutdown now leads to this graceful statemachine shutdown via the wait queue.
- Link to v2: https://patch.msgid.link/20260710-isotp-fixes-v2-0-bc57e26594b2@hartkopp.net
Changes in v2:
- patch 2: shutdown state machine to fix sashiko-bot complains
Link: https://lore.kernel.org/linux-can/20260710094807.A50DD1F000E9@smtp.kernel.org/
- Link to v1: https://patch.msgid.link/20260710-isotp-fixes-v1-0-75a1d11d7df9@hartkopp.net
---
Oliver Hartkopp (4):
can: isotp: use unconditional synchronize_rcu() in isotp_release()
can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER
can: isotp: fix race between RX timer and consecutive frame reception
can: isotp: fix lock-free state transition in tx timer handler
net/can/isotp.c | 115 ++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 83 insertions(+), 32 deletions(-)
---
base-commit: a635d6748234582ea287c5ffeae28b9b23f91c7e
change-id: 20260710-isotp-fixes-ead7480d67bf
Best regards,
--
Oliver Hartkopp <socketcan@hartkopp.net>
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() 2026-07-10 16:47 [PATCH v5 0/4] net: can: isotp-fixes Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 ` Oliver Hartkopp via B4 Relay 2026-07-10 17:05 ` sashiko-bot 2026-07-10 16:47 ` [PATCH v5 2/4] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER Oliver Hartkopp via B4 Relay ` (2 subsequent siblings) 3 siblings, 1 reply; 9+ messages in thread From: Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 UTC (permalink / raw) To: linux-can; +Cc: Oliver Hartkopp, Marc Kleine-Budde, Nico Yip From: Oliver Hartkopp <socketcan@hartkopp.net> isotp_notify() unregisters the (RCU) CAN filters via can_rx_unregister() and clears so->bound without waiting for a grace period. isotp_release() uses so->bound to decide whether it needs to call synchronize_rcu() before cancelling so->rxtimer, so when NETDEV_UNREGISTER runs first it skips that synchronize_rcu() and can cancel the timer while an in-flight isotp_rcv() is still executing and about to re-arm it via isotp_send_fc(), leading to a use-after-free timer callback on the freed socket. sakisho-bot remarked a problem with rtnl_lock held in isotp_notify(), therefore make isotp_release() always call synchronize_rcu() before cancelling the timers, regardless of so->bound. This still closes the original race (isotp_notify() clearing so->bound without waiting for in-flight isotp_rcv() callers before isotp_release() cancels the RX timer) without adding any RCU wait to the netdevice notifier path. Fixes: 14a4696bc311 ("can: isotp: isotp_release(): omit unintended hrtimer restart on socket release") Closes: https://lore.kernel.org/linux-can/20260707085210.6B6C01F000E9@smtp.kernel.org/ Reported-by: Nico Yip <zdi-disclosures@trendmicro.com> (ZDI-CAN-31764) Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net> --- net/can/isotp.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index c48b4a818297..d30937345bcd 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -1235,15 +1235,22 @@ static int isotp_release(struct socket *sock) can_rx_unregister(net, dev, so->txid, SINGLE_MASK(so->txid), isotp_rcv_echo, sk); dev_put(dev); - synchronize_rcu(); } } } + /* Always wait for a grace period before touching the timers below. + * A concurrent NETDEV_UNREGISTER may have already unregistered our + * filters and cleared so->bound in isotp_notify() without waiting + * for in-flight isotp_rcv() callers to finish, so this call must not + * be skipped just because so->bound is already 0 here. + */ + synchronize_rcu(); + hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); hrtimer_cancel(&so->rxtimer); so->ifindex = 0; -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() 2026-07-10 16:47 ` [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() Oliver Hartkopp via B4 Relay @ 2026-07-10 17:05 ` sashiko-bot 0 siblings, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-07-10 17:05 UTC (permalink / raw) To: Oliver Hartkopp Cc: Vincent Mailhol, linux-can, Marc Kleine-Budde, Oliver Hartkopp, Oleksij Rempel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] A race condition between `isotp_release()` and a device network namespace move (or unregistration) causes `isotp_release()` to skip calling `can_rx_unregister()`. This leaves a dynamically allocated `struct receiver` linked in the CAN device's `dev_rcv_lists`, holding a dangling pointer to the socket `sk` which is subsequently freed. When the device receives packets in the new network namespace, this triggers a use-after-free on the socket. -- --- Patch [1]: [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() --- commit f75831bce5f2c912b988e63e3d43f06bda5b25c6 Author: Oliver Hartkopp <socketcan@hartkopp.net> can: isotp: use unconditional synchronize_rcu() in isotp_release() isotp_notify() unregisters the CAN filters and clears so->bound without waiting for a grace period. This commit updates isotp_release() to always call synchronize_rcu() before cancelling timers to prevent a use-after-free timer callback on a freed socket. Closes: https://lore.kernel.org/linux-can/20260707085210.6B6C01F000E9@smtp.kernel.org/ > diff --git a/net/can/isotp.c b/net/can/isotp.c > index c48b4a818297e..d30937345bcdc 100644 > --- a/net/can/isotp.c > +++ b/net/can/isotp.c > @@ -1237,11 +1237,18 @@ static int isotp_release(struct socket *sock) > SINGLE_MASK(so->txid), > isotp_rcv_echo, sk); > dev_put(dev); [Severity: High] This is a pre-existing issue, but does this code have a race condition when a device is moved to another network namespace? If a CAN device is moved to a new namespace, dev_get_by_index() called earlier in isotp_release() will return NULL: dev = dev_get_by_index(net, so->ifindex); if (dev) { if (isotp_register_rxid(so)) can_rx_unregister(net, dev, so->rxid, ... This causes isotp_release() to silently skip calling can_rx_unregister(). At the same time, isotp_release() prematurely removes the socket from isotp_notifier_list. As a result, the notifier iteration skips the socket: isotp_notifier() { ... list_for_each_entry(isotp_busy_notifier, &isotp_notifier_list, notifier) { spin_unlock(&isotp_notifier_lock); isotp_notify(isotp_busy_notifier, msg, dev); ... } This prevents the fallback unregistration in isotp_notify(). This leaves a dynamically allocated struct receiver linked in the CAN device's dev_rcv_lists with a dangling pointer to the socket, which is subsequently freed. When the device receives packets in the new network namespace, could this trigger a use-after-free on the socket? > - synchronize_rcu(); > } > } > } > > + /* Always wait for a grace period before touching the timers below. > + * A concurrent NETDEV_UNREGISTER may have already unregistered our > + * filters and cleared so->bound in isotp_notify() without waiting > + * for in-flight isotp_rcv() callers to finish, so this call must not > + * be skipped just because so->bound is already 0 here. > + */ > + synchronize_rcu(); > + > hrtimer_cancel(&so->txfrtimer); > hrtimer_cancel(&so->txtimer); > hrtimer_cancel(&so->rxtimer); -- Sashiko AI review · https://sashiko.dev/#/patchset/20260710-isotp-fixes-v5-0-15174d82d6f1@hartkopp.net?part=1 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 2/4] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER 2026-07-10 16:47 [PATCH v5 0/4] net: can: isotp-fixes Oliver Hartkopp via B4 Relay 2026-07-10 16:47 ` [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 ` Oliver Hartkopp via B4 Relay 2026-07-10 16:58 ` sashiko-bot 2026-07-10 16:47 ` [PATCH v5 3/4] can: isotp: fix race between RX timer and consecutive frame reception Oliver Hartkopp via B4 Relay 2026-07-10 16:47 ` [PATCH v5 4/4] can: isotp: fix lock-free state transition in tx timer handler Oliver Hartkopp via B4 Relay 3 siblings, 1 reply; 9+ messages in thread From: Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 UTC (permalink / raw) To: linux-can; +Cc: Oliver Hartkopp, Marc Kleine-Budde, sashiko-bot From: Oliver Hartkopp <socketcan@hartkopp.net> isotp_release() looked up the bound network device via dev_get_by_index() using the stored ifindex. During device unregistration the device is unlisted from the ifindex hash before the NETDEV_UNREGISTER notifier chain runs, so a concurrent isotp_release() could find no device, skip can_rx_unregister() entirely, and still proceed to free the socket. Since isotp_release() had already removed itself from the isotp notifier list at that point, isotp_notify() would never get a chance to clean up either, leaving a stale CAN filter that keeps pointing at the freed socket. Fix this the same way raw.c already does: hold a tracked reference to the bound net_device in the socket (so->dev/so->dev_tracker) from bind() onward instead of re-resolving it from the ifindex, and serialize bind()/release() with rtnl_lock() so that so->dev is always consistent with what the NETDEV_UNREGISTER notifier sees. so->dev stays valid regardless of ifindex-hash unlisting, and is only ever cleared by whichever of isotp_release()/isotp_notify() gets there first, so the filter is always removed exactly once. isotp_bind() now rejects a (re)bind with -EAGAIN while so->tx.state isn't ISOTP_IDLE yet, so a timer left running by a prior NETDEV_UNREGISTER can't act on a newly bound so->ifindex. Both checks share the same lock_sock() section, so there is no window in which a concurrent isotp_notify() clearing so->bound could be missed. Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net> --- net/can/isotp.c | 87 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index d30937345bcd..e7597ca3e140 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -150,10 +150,12 @@ struct tpcon { struct isotp_sock { struct sock sk; int bound; int ifindex; + struct net_device *dev; + netdevice_tracker dev_tracker; canid_t txid; canid_t rxid; ktime_t tx_gap; ktime_t lastrxcf_tstamp; struct hrtimer rxtimer, txtimer, txfrtimer; @@ -976,10 +978,18 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); if (err) goto err_event_drop; } + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ + if (!so->bound) { + err = -EADDRNOTAVAIL; + goto err_out_drop; + } + /* PDU size > default => try max_pdu_size */ if (size > so->tx.buflen && so->tx.buflen < max_pdu_size) { u8 *newbuf = kmalloc(max_pdu_size, GFP_KERNEL); if (newbuf) { @@ -1217,32 +1227,34 @@ static int isotp_release(struct socket *sock) spin_lock(&isotp_notifier_lock); } list_del(&so->notifier); spin_unlock(&isotp_notifier_lock); + rtnl_lock(); lock_sock(sk); - /* remove current filters & unregister */ - if (so->bound) { - if (so->ifindex) { - struct net_device *dev; - - dev = dev_get_by_index(net, so->ifindex); - if (dev) { - if (isotp_register_rxid(so)) - can_rx_unregister(net, dev, so->rxid, - SINGLE_MASK(so->rxid), - isotp_rcv, sk); - - can_rx_unregister(net, dev, so->txid, - SINGLE_MASK(so->txid), - isotp_rcv_echo, sk); - dev_put(dev); - } - } + /* remove current filters & unregister + * tracked reference so->dev is taken at bind() time with rtnl_lock + */ + if (so->bound && so->dev) { + if (isotp_register_rxid(so)) + can_rx_unregister(net, so->dev, so->rxid, + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + + can_rx_unregister(net, so->dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); + netdev_put(so->dev, &so->dev_tracker); } + so->ifindex = 0; + so->bound = 0; + so->dev = NULL; + + rtnl_unlock(); + /* Always wait for a grace period before touching the timers below. * A concurrent NETDEV_UNREGISTER may have already unregistered our * filters and cleared so->bound in isotp_notify() without waiting * for in-flight isotp_rcv() callers to finish, so this call must not * be skipped just because so->bound is already 0 here. @@ -1251,13 +1263,10 @@ static int isotp_release(struct socket *sock) hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); hrtimer_cancel(&so->rxtimer); - so->ifindex = 0; - so->bound = 0; - sock_orphan(sk); sock->sk = NULL; release_sock(sk); sock_prot_inuse_add(net, sk->sk_prot, -1); @@ -1308,17 +1317,29 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l } if (!addr->can_ifindex) return -ENODEV; + rtnl_lock(); lock_sock(sk); if (so->bound) { err = -EINVAL; goto out; } + /* A transmission that outlived a previous binding (unbound by + * NETDEV_UNREGISTER) may still be draining; the FC/echo watchdog + * timers bound how long this takes. Checked together with + * so->bound in the same lock_sock() section above, so there is no + * window in which a concurrent isotp_notify() could be missed. + */ + if (so->tx.state != ISOTP_IDLE) { + err = -EAGAIN; + goto out; + } + /* ensure different CAN IDs when the rx_id is to be registered */ if (isotp_register_rxid(so) && rx_id == tx_id) { err = -EADDRNOTAVAIL; goto out; } @@ -1327,18 +1348,16 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l if (!dev) { err = -ENODEV; goto out; } if (dev->type != ARPHRD_CAN) { - dev_put(dev); err = -ENODEV; - goto out; + goto out_put_dev; } if (READ_ONCE(dev->mtu) < so->ll.mtu) { - dev_put(dev); err = -EINVAL; - goto out; + goto out_put_dev; } if (!(dev->flags & IFF_UP)) notify_enetdown = 1; ifindex = dev->ifindex; @@ -1352,20 +1371,29 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l /* register for echo skb's */ can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id), isotp_rcv_echo, sk, "isotpe", sk); - dev_put(dev); - /* switch to new settings */ so->ifindex = ifindex; so->rxid = rx_id; so->txid = tx_id; so->bound = 1; + /* bind() ok -> hold a reference for so->dev so that isotp_release() + * can safely reach the device later, even if a concurrent + * NETDEV_UNREGISTER has already unlisted it by ifindex. + */ + so->dev = dev; + netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL); + +out_put_dev: + /* remove potential reference from dev_get_by_index() */ + dev_put(dev); out: release_sock(sk); + rtnl_unlock(); if (notify_enetdown) { sk->sk_err = ENETDOWN; if (!sock_flag(sk, SOCK_DEAD)) sk_error_report(sk); @@ -1564,11 +1592,11 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, struct sock *sk = &so->sk; if (!net_eq(dev_net(dev), sock_net(sk))) return; - if (so->ifindex != dev->ifindex) + if (so->dev != dev) return; switch (msg) { case NETDEV_UNREGISTER: lock_sock(sk); @@ -1580,14 +1608,16 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, isotp_rcv, sk); can_rx_unregister(dev_net(dev), dev, so->txid, SINGLE_MASK(so->txid), isotp_rcv_echo, sk); + netdev_put(so->dev, &so->dev_tracker); } so->ifindex = 0; so->bound = 0; + so->dev = NULL; release_sock(sk); sk->sk_err = ENODEV; if (!sock_flag(sk, SOCK_DEAD)) sk_error_report(sk); @@ -1643,10 +1673,11 @@ static int isotp_init(struct sock *sk) { struct isotp_sock *so = isotp_sk(sk); so->ifindex = 0; so->bound = 0; + so->dev = NULL; so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS; so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; so->opt.rx_ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; so->opt.rxpad_content = CAN_ISOTP_DEFAULT_PAD_CONTENT; -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v5 2/4] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER 2026-07-10 16:47 ` [PATCH v5 2/4] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER Oliver Hartkopp via B4 Relay @ 2026-07-10 16:58 ` sashiko-bot 0 siblings, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-07-10 16:58 UTC (permalink / raw) To: Oliver Hartkopp Cc: Oliver Hartkopp, Marc Kleine-Budde, Oleksij Rempel, Vincent Mailhol, linux-can Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] Missing rx.state validation in isotp_bind() allows cross-interface data corruption and spurious timeouts. -- --- Patch [2]: [PATCH v5 2/4] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER --- commit 84cdeb4490291132fa60d9c7c68c759beafa051d Author: Oliver Hartkopp <socketcan@hartkopp.net> can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER This patch fixes a use-after-free race by holding a tracked reference to the bound net_device in the socket from bind() onward. It also serializes bind() and release() with rtnl_lock() and adds a check to reject binding if the transmission state is not idle. Link: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ > --- a/net/can/isotp.c > +++ b/net/can/isotp.c [ ... ] > @@ -1317,6 +1327,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l > goto out; > } > > + /* A transmission that outlived a previous binding (unbound by > + * NETDEV_UNREGISTER) may still be draining; the FC/echo watchdog > + * timers bound how long this takes. Checked together with > + * so->bound in the same lock_sock() section above, so there is no > + * window in which a concurrent isotp_notify() could be missed. > + */ > + if (so->tx.state != ISOTP_IDLE) { > + err = -EAGAIN; > + goto out; > + } > + [Severity: High] This is a pre-existing issue, but should we also validate so->rx.state here? If a multi-frame ISOTP message is being received (ISOTP_WAIT_DATA state with rxtimer armed) during a concurrent NETDEV_UNREGISTER, the socket is unbound but so->rx.state and rxtimer remain active. If the user quickly re-binds to a new interface, would consecutive CAN frames on the newly bound interface be erroneously appended to the old buffer from the deleted interface? Could this also cause the legacy rxtimer to spuriously set sk_err = ETIMEDOUT on the new connection? > /* ensure different CAN IDs when the rx_id is to be registered */ > if (isotp_register_rxid(so) && rx_id == tx_id) { > err = -EADDRNOTAVAIL; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260710-isotp-fixes-v5-0-15174d82d6f1@hartkopp.net?part=2 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 3/4] can: isotp: fix race between RX timer and consecutive frame reception 2026-07-10 16:47 [PATCH v5 0/4] net: can: isotp-fixes Oliver Hartkopp via B4 Relay 2026-07-10 16:47 ` [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() Oliver Hartkopp via B4 Relay 2026-07-10 16:47 ` [PATCH v5 2/4] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 ` Oliver Hartkopp via B4 Relay 2026-07-10 17:02 ` sashiko-bot 2026-07-10 16:47 ` [PATCH v5 4/4] can: isotp: fix lock-free state transition in tx timer handler Oliver Hartkopp via B4 Relay 3 siblings, 1 reply; 9+ messages in thread From: Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 UTC (permalink / raw) To: linux-can; +Cc: Oliver Hartkopp, Marc Kleine-Budde, sashiko-bot From: Oliver Hartkopp <socketcan@hartkopp.net> When receiving a Consecutive Frame (CF), hrtimer_cancel() is called to stop the RX timer. However, if the timer handler (isotp_rx_timer_handler) has already fired and is running concurrently on another CPU, hrtimer_cancel() will wait for it to finish. By the time it returns, the timer handler might have already reported an ETIMEDOUT error and reset the RX state machine. If we blindly continue processing the CF, we corrupt the newly reset or idle state. Fix this by verifying that the state is still ISOTP_WAIT_DATA after canceling the hrtimer. If it changed, the reception has already timed out, so we drop the frame. Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net> --- net/can/isotp.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/can/isotp.c b/net/can/isotp.c index e7597ca3e140..c0edbd35516c 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -575,10 +575,18 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, so->lastrxcf_tstamp = skb->tstamp; } hrtimer_cancel(&so->rxtimer); + /* isotp_rx_timer_handler() may have raced us for so->rx.state + * while hrtimer_cancel() above waited for it to finish, already + * reporting ETIMEDOUT and resetting the reception; don't process + * this CF into a reassembly that has already been given up on. + */ + if (so->rx.state != ISOTP_WAIT_DATA) + return 1; + /* CFs are never longer than the FF */ if (cf->len > so->rx.ll_dl) return 1; /* CFs have usually the LL_DL length */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v5 3/4] can: isotp: fix race between RX timer and consecutive frame reception 2026-07-10 16:47 ` [PATCH v5 3/4] can: isotp: fix race between RX timer and consecutive frame reception Oliver Hartkopp via B4 Relay @ 2026-07-10 17:02 ` sashiko-bot 0 siblings, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-07-10 17:02 UTC (permalink / raw) To: Oliver Hartkopp Cc: Marc Kleine-Budde, Oliver Hartkopp, Vincent Mailhol, linux-can, Oleksij Rempel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] Missing state validation after hrtimer_cancel in the TX path allows resurrection of aborted transmissions and state machine corruption. -- --- Patch [3]: [PATCH v5 3/4] can: isotp: fix race between RX timer and consecutive frame reception --- commit 8ee7739c2adf82e961ff6ad45d8c572723ba74e7 Author: Oliver Hartkopp <socketcan@hartkopp.net> can: isotp: fix race between RX timer and consecutive frame reception When receiving a Consecutive Frame (CF), hrtimer_cancel() is called to stop the RX timer. The patch verifies that the state is still ISOTP_WAIT_DATA after canceling the hrtimer to avoid state machine corruption. Link: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ > diff --git a/net/can/isotp.c b/net/can/isotp.c > index e7597ca3e140b..c0edbd35516cc 100644 > --- a/net/can/isotp.c > +++ b/net/can/isotp.c > @@ -577,6 +577,14 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, > > hrtimer_cancel(&so->rxtimer); > > + /* isotp_rx_timer_handler() may have raced us for so->rx.state > + * while hrtimer_cancel() above waited for it to finish, already > + * reporting ETIMEDOUT and resetting the reception; don't process > + * this CF into a reassembly that has already been given up on. > + */ > + if (so->rx.state != ISOTP_WAIT_DATA) > + return 1; > + [Severity: High] This is a pre-existing issue, but does a similar race condition exist in the TX paths? Looking at isotp_rcv_fc(), we call hrtimer_cancel() on the txtimer without validating the TX state afterwards: net/can/isotp.c:isotp_rcv_fc() { ... hrtimer_cancel(&so->txtimer); if ((cf->len < ae + FC_CONTENT_SZ) || ... } Similarly in isotp_rcv_echo(): net/can/isotp.c:isotp_rcv_echo() { ... /* cancel local echo timeout */ hrtimer_cancel(&so->txtimer); /* local echo skb with consecutive frame has been consumed */ so->cfecho = 0; ... } If a flow control or echo CAN frame is received at the exact same time the TX timer expires, could isotp_rcv_fc() or isotp_rcv_echo() block in hrtimer_cancel() waiting for isotp_tx_timer_handler() to finish? If the timer handler sets so->tx.state = ISOTP_IDLE and fails the socket, could the receiver functions then blindly resume the transmission, potentially resurrecting an aborted transfer and corrupting the state machine? Furthermore, since the state would briefly be ISOTP_IDLE, might a concurrent sendmsg() succeed, resulting in two threads concurrently modifying the TX buffer and state? > /* CFs are never longer than the FF */ > if (cf->len > so->rx.ll_dl) > return 1; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260710-isotp-fixes-v5-0-15174d82d6f1@hartkopp.net?part=3 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 4/4] can: isotp: fix lock-free state transition in tx timer handler 2026-07-10 16:47 [PATCH v5 0/4] net: can: isotp-fixes Oliver Hartkopp via B4 Relay ` (2 preceding siblings ...) 2026-07-10 16:47 ` [PATCH v5 3/4] can: isotp: fix race between RX timer and consecutive frame reception Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 ` Oliver Hartkopp via B4 Relay 2026-07-10 17:02 ` sashiko-bot 3 siblings, 1 reply; 9+ messages in thread From: Oliver Hartkopp via B4 Relay @ 2026-07-10 16:47 UTC (permalink / raw) To: linux-can; +Cc: Oliver Hartkopp, Marc Kleine-Budde, sashiko-bot From: Oliver Hartkopp <socketcan@hartkopp.net> Commit 051737439eae ("can: isotp: fix race between isotp_sendsmg() and isotp_release()") introduced a lock-free state machine check to prevent race conditions between the TX timer and concurrent state updates. However, the original patch missed replacing the initial state checks and left the late assignment of ISOTP_IDLE as a blind, non-atomic write. Fix this by properly sampling the initial state into 'old_state' and using cmpxchg() to atomically move the state to ISOTP_IDLE. If the state changed concurrently (e.g., due to an incoming echo or a new sendmsg), the timeout is stale and we bail out safely without corrupting the state machine. Fixes: 43a08c3bdac4cb ("can: isotp: isotp_sendmsg(): fix TX buffer concurrent access in isotp_sendmsg()") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net> --- net/can/isotp.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index c0edbd35516c..e49a58e188e6 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -920,24 +920,29 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) { struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, txtimer); struct sock *sk = &so->sk; + u32 old_state = so->tx.state; /* don't handle timeouts in IDLE or SHUTDOWN state */ - if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN) + if (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN) + return HRTIMER_NORESTART; + + /* Only claim timeout if state is unchanged to avoid overwriting + * concurrent state updates from isotp_sendmsg/isotp_rcv_echo. + */ + if (cmpxchg(&so->tx.state, old_state, ISOTP_IDLE) != old_state) return HRTIMER_NORESTART; /* we did not get any flow control or echo frame in time */ /* report 'communication error on send' */ sk->sk_err = ECOMM; if (!sock_flag(sk, SOCK_DEAD)) sk_error_report(sk); - /* reset tx state */ - so->tx.state = ISOTP_IDLE; wake_up_interruptible(&so->wait); return HRTIMER_NORESTART; } -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v5 4/4] can: isotp: fix lock-free state transition in tx timer handler 2026-07-10 16:47 ` [PATCH v5 4/4] can: isotp: fix lock-free state transition in tx timer handler Oliver Hartkopp via B4 Relay @ 2026-07-10 17:02 ` sashiko-bot 0 siblings, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-07-10 17:02 UTC (permalink / raw) To: Oliver Hartkopp Cc: linux-can, Vincent Mailhol, Oliver Hartkopp, Oleksij Rempel, Marc Kleine-Budde Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: New issues: - [Medium] Missing READ_ONCE() on lockless state read allows compiler load tearing to defeat cmpxchg protection Pre-existing issues: - [High] TOCTOU vulnerability in RX handlers allows state machine corruption and out-of-bounds reads - [High] Unsafe teardown order in error path releases state machine before canceling async timers - [High] Premature state release causes misattribution of timeout errors to concurrent transmissions -- --- Patch [4]: [PATCH v5 4/4] can: isotp: fix lock-free state transition in tx timer handler --- commit 1142eb2123699c3ef59ba2ca431beca7fc493cd1 Author: Oliver Hartkopp <socketcan@hartkopp.net> can: isotp: fix lock-free state transition in tx timer handler Commit 051737439eae ("can: isotp: fix race between isotp_sendsmg() and isotp_release()") introduced a lock-free state machine check to prevent race conditions between the TX timer and concurrent state updates. However, the original patch missed replacing the initial state checks and left the late assignment of ISOTP_IDLE as a blind, non-atomic write. Link: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ > diff --git a/net/can/isotp.c b/net/can/isotp.c > index c0edbd35516cc..e49a58e188e6b 100644 > --- a/net/can/isotp.c > +++ b/net/can/isotp.c > @@ -922,9 +922,16 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) > struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, > txtimer); > struct sock *sk = &so->sk; > + u32 old_state = so->tx.state; [Severity: Medium] Could this lockless state read be subject to compiler load tearing without a READ_ONCE()? If the compiler decides to reload so->tx.state directly into the cmpxchg macro arguments under register pressure, and the state changes concurrently (e.g., to ISOTP_SENDING), wouldn't the reloaded value cause cmpxchg to compare the new state against itself? Could this blind success erroneously abort the new transmission? > > /* don't handle timeouts in IDLE or SHUTDOWN state */ > - if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN) > + if (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN) > + return HRTIMER_NORESTART; > + > + /* Only claim timeout if state is unchanged to avoid overwriting > + * concurrent state updates from isotp_sendmsg/isotp_rcv_echo. > + */ > + if (cmpxchg(&so->tx.state, old_state, ISOTP_IDLE) != old_state) [Severity: High] This isn't a bug introduced by this patch, but does releasing the state machine ownership here cause misattribution of timeout errors to concurrent transmissions? Since the state is transitioned to ISOTP_IDLE before setting sk->sk_err and calling wake_up_interruptible(), could a concurrent sender claim ISOTP_SENDING in this window? If so, when the wait queue is woken, the original timed-out thread might wake up, see the state is no longer ISOTP_IDLE, and go back to sleep. Could the new sender later complete successfully but read the stale sk->sk_err = ECOMM left by the timer, consuming the error, while the original thread eventually wakes up and incorrectly reads success? > return HRTIMER_NORESTART; > > /* we did not get any flow control or echo frame in time */ > @@ -934,8 +941,6 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) > if (!sock_flag(sk, SOCK_DEAD)) > sk_error_report(sk); > > - /* reset tx state */ > - so->tx.state = ISOTP_IDLE; > wake_up_interruptible(&so->wait); > > return HRTIMER_NORESTART; [Severity: High] This is a pre-existing issue, but while reviewing the state machine changes, is there a TOCTOU vulnerability in the RX handlers? Looking at isotp_rcv_fc() in net/can/isotp.c: if (so->tx.state != ISOTP_WAIT_FC && ...) return 0; hrtimer_cancel(&so->txtimer); // ... blindly proceeds to send The state is checked before the blocking hrtimer_cancel() and never re-verified. If a concurrent timeout aborts the transmission (setting state to ISOTP_IDLE) while the RX thread is blocked in hrtimer_cancel(), and a new thread enters isotp_sendmsg(), claims ISOTP_SENDING, and begins mutating so->tx.buf, what happens when hrtimer_cancel() unblocks? Would the RX handler blindly assume its earlier state check is still valid, overwrite the state, and call isotp_send_cframe(), reading from the new thread's partially initialized buffer? A similar pattern appears in isotp_rcv_echo(): if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) return; hrtimer_cancel(&so->txtimer); // ... checks state but cfecho check was bypassed The cfecho validation occurs before hrtimer_cancel(), allowing a concurrent new transmission to slip past the check. [Severity: High] This is a pre-existing issue, but does isotp_sendmsg() have an unsafe teardown order in its error path? Looking at err_event_drop in isotp_sendmsg(): err_event_drop: /* got signal: force tx state machine to be idle */ so->tx.state = ISOTP_IDLE; hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); If isotp_sendmsg() is interrupted by a signal, it sets so->tx.state = ISOTP_IDLE before canceling the asynchronous timers. Could a concurrent thread immediately enter isotp_sendmsg(), claim the state, and start configuring so->tx? If so, could the original thread's un-canceled txfrtimer then fire, see the new thread's ISOTP_SENDING state, and prematurely call isotp_send_cframe() on the new thread's incomplete buffer? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260710-isotp-fixes-v5-0-15174d82d6f1@hartkopp.net?part=4 ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-07-10 17:05 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-10 16:47 [PATCH v5 0/4] net: can: isotp-fixes Oliver Hartkopp via B4 Relay 2026-07-10 16:47 ` [PATCH v5 1/4] can: isotp: use unconditional synchronize_rcu() in isotp_release() Oliver Hartkopp via B4 Relay 2026-07-10 17:05 ` sashiko-bot 2026-07-10 16:47 ` [PATCH v5 2/4] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER Oliver Hartkopp via B4 Relay 2026-07-10 16:58 ` sashiko-bot 2026-07-10 16:47 ` [PATCH v5 3/4] can: isotp: fix race between RX timer and consecutive frame reception Oliver Hartkopp via B4 Relay 2026-07-10 17:02 ` sashiko-bot 2026-07-10 16:47 ` [PATCH v5 4/4] can: isotp: fix lock-free state transition in tx timer handler Oliver Hartkopp via B4 Relay 2026-07-10 17:02 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox