From: Sasha Levin Date: Sun, 26 Jul 2026 12:04:50 +0000 (-0400) Subject: Fixes for all trees X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=74b7d8f82e88d63f608c8e5d6e5c5b1eee1a753b;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-5.10/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch b/queue-5.10/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch new file mode 100644 index 0000000000..6b7b02d078 --- /dev/null +++ b/queue-5.10/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch @@ -0,0 +1,238 @@ +From 0e74c56e42514516ffe21b4971c66a9395662a82 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:27:18 +0200 +Subject: can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER + +From: Oliver Hartkopp + +commit 20bab8b88baac140ca3701116e1d486c7f51e311 upstream. + +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|rx].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 +Closes: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-2-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 88 +++++++++++++++++++++++++++++++++---------------- + 1 file changed, 59 insertions(+), 29 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index c914b8a09155b3..169fb17d0f0e16 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -137,6 +137,7 @@ struct isotp_sock { + struct sock sk; + int bound; + int ifindex; ++ struct net_device *dev; + canid_t txid; + canid_t rxid; + ktime_t tx_gap; +@@ -939,6 +940,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + 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; ++ } ++ + if (!size || size > MAX_MSG_LENGTH) { + err = -EINVAL; + goto err_out_drop; +@@ -1166,28 +1175,30 @@ static int isotp_release(struct socket *sock) + 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); ++ dev_put(so->dev); + } + ++ 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 +@@ -1200,9 +1211,6 @@ static int isotp_release(struct socket *sock) + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->rxtimer); + +- so->ifindex = 0; +- so->bound = 0; +- + sock_orphan(sk); + sock->sk = NULL; + +@@ -1256,6 +1264,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + if (!addr->can_ifindex) + return -ENODEV; + ++ rtnl_lock(); + lock_sock(sk); + + if (so->bound) { +@@ -1263,6 +1272,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + ++ /* A transmission or reception that outlived a previous binding ++ * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo ++ * and RX 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 || so->rx.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; +@@ -1275,14 +1295,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + if (dev->type != ARPHRD_CAN) { +- dev_put(dev); + err = -ENODEV; +- goto out; ++ goto out_put_dev; + } +- if (dev->mtu < so->ll.mtu) { +- dev_put(dev); ++ if (READ_ONCE(dev->mtu) < so->ll.mtu) { + err = -EINVAL; +- goto out; ++ goto out_put_dev; + } + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; +@@ -1300,16 +1318,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + 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; ++ dev_hold(so->dev); ++ ++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; +@@ -1512,7 +1539,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + if (!net_eq(dev_net(dev), sock_net(sk))) + return; + +- if (so->ifindex != dev->ifindex) ++ if (so->dev != dev) + return; + + switch (msg) { +@@ -1528,10 +1555,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + can_rx_unregister(dev_net(dev), dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); ++ dev_put(so->dev); + } + + so->ifindex = 0; + so->bound = 0; ++ so->dev = NULL; + release_sock(sk); + + sk->sk_err = ENODEV; +@@ -1576,6 +1605,7 @@ static int isotp_init(struct sock *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; +-- +2.53.0 + diff --git a/queue-5.10/can-isotp-serialize-tx-state-transitions-under-so-rx.patch b/queue-5.10/can-isotp-serialize-tx-state-transitions-under-so-rx.patch new file mode 100644 index 0000000000..376668bb92 --- /dev/null +++ b/queue-5.10/can-isotp-serialize-tx-state-transitions-under-so-rx.patch @@ -0,0 +1,426 @@ +From 95444e5a3d79567ba3e4dc19ec534072c8628e67 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:27:19 +0200 +Subject: can: isotp: serialize TX state transitions under so->rx_lock + +From: Oliver Hartkopp + +commit cf070fe33bfbd1a4c21236078fadb35dd223a157 upstream. + +The TX state machine (so->tx.state) is driven from three contexts: +sendmsg() claiming and progressing a transfer, the RX path consuming +Flow Control/echo frames, and two hrtimers timing out a stalled +transfer. Mixing a lock-free cmpxchg() claim in sendmsg() with +hrtimer_cancel() calls made under so->rx_lock elsewhere left windows +where a frame or timer callback could act on a state that had already +moved on, corrupting an unrelated transfer. + +so->rx_lock now covers the full lifecycle of a TX claim: sendmsg() +takes it to check so->tx.state is ISOTP_IDLE, switch it to +ISOTP_SENDING, bump so->tx_gen and drain the previous transfer's +timers - all as one critical section. isotp_rcv_fc()/isotp_rcv_cf() +already run under this lock via isotp_rcv(), and isotp_rcv_echo() now +takes it itself, so none of them can ever observe a transfer mid-claim. +This also means a transfer can no longer be handed to sendmsg()'s +cleanup paths (signal or send error) while another thread is +concurrently claiming or finishing it, so those paths can cancel +timers and reset the state unconditionally. + +isotp_release() claims the socket the same way, so a racing sendmsg() +sees a consistent ISOTP_SHUTDOWN and skips arming its timer or sending. + +Only the hrtimer callbacks stay outside so->rx_lock, since they run +under so->rx_lock's cancellation elsewhere and taking it themselves +would deadlock. so->tx_gen lets them recognize whether the transfer +they timed out is still the one currently active, so they don't +report an error against a transfer that has since completed or been +superseded. + +Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-3-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 192 ++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 154 insertions(+), 38 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index 169fb17d0f0e16..d493b66ae8d29f 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -142,7 +142,7 @@ struct isotp_sock { + canid_t rxid; + ktime_t tx_gap; + ktime_t lastrxcf_tstamp; +- struct hrtimer rxtimer, txtimer, txfrtimer; ++ struct hrtimer rxtimer, txtimer, txfrtimer, echotimer; + struct can_isotp_options opt; + struct can_isotp_fc_options rxfc, txfc; + struct can_isotp_ll_options ll; +@@ -150,6 +150,7 @@ struct isotp_sock { + u32 force_tx_stmin; + u32 force_rx_stmin; + u32 cfecho; /* consecutive frame echo tag */ ++ u32 tx_gen; /* generation, bumped per new tx transfer */ + struct tpcon rx, tx; + struct list_head notifier; + wait_queue_head_t wait; +@@ -356,6 +357,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + + hrtimer_cancel(&so->txtimer); + ++ /* isotp_tx_timeout() may have given up on this job while ++ * hrtimer_cancel() above waited for it to finish; so->rx_lock ++ * (held by our caller isotp_rcv()) rules out a concurrent claim, ++ * so a plain recheck is enough here. ++ */ ++ if (so->tx.state != ISOTP_WAIT_FC && ++ so->tx.state != ISOTP_WAIT_FIRST_FC) ++ return 1; ++ + if ((cf->len < ae + FC_CONTENT_SZ) || + ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { +@@ -401,7 +411,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + so->tx.bs = 0; + so->tx.state = ISOTP_SENDING; + /* send CF frame and enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); + break; +@@ -544,6 +554,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; ++ + /* CFs are never longer than the FF */ + if (cf->len > so->rx.ll_dl) + return 1; +@@ -833,20 +851,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + + /* only handle my own local echo CF/SF skb's (no FF!) */ +- if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) ++ if (skb->sk != sk) + return; + ++ /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock ++ * (no isotp_rcv() caller here), so take it ourselves ++ */ ++ spin_lock(&so->rx_lock); ++ ++ /* so->cfecho may since belong to a new transfer; recheck under lock */ ++ if (so->cfecho != *(u32 *)cf->data) ++ goto out_unlock; ++ + /* cancel local echo timeout */ +- hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + + /* local echo skb with consecutive frame has been consumed */ + so->cfecho = 0; + ++ /* claiming a transfer also takes so->rx_lock, so a plain recheck ++ * is enough: so->tx.state can't have flipped to ISOTP_SENDING for ++ * a new claim while we're still in here ++ */ ++ if (so->tx.state != ISOTP_SENDING) ++ goto out_unlock; ++ + if (so->tx.idx >= so->tx.len) { + /* we are done */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); +- return; ++ goto out_unlock; + } + + if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { +@@ -854,53 +888,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + so->tx.state = ISOTP_WAIT_FC; + hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); +- return; ++ goto out_unlock; + } + + /* no gap between data frames needed => use burst mode */ + if (!so->tx_gap) { + /* enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); +- return; ++ goto out_unlock; + } + + /* start timer to send next consecutive frame with correct delay */ + hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT); ++ ++out_unlock: ++ spin_unlock(&so->rx_lock); + } + +-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get ++ * cancelled under so->rx_lock elsewhere, so this must stay lock-free to ++ * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting ++ * a new transfer with an error from the one that just timed out. ++ */ ++static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) + { +- struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, +- txtimer); + struct sock *sk = &so->sk; ++ u32 gen = READ_ONCE(so->tx_gen); ++ u32 old_state = READ_ONCE(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 the timeout if the state is still unchanged */ ++ 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->sk_error_report(sk); ++ if (READ_ONCE(so->tx_gen) == gen) { ++ /* report 'communication error on send' */ ++ sk->sk_err = ECOMM; ++ if (!sock_flag(sk, SOCK_DEAD)) ++ sk->sk_error_report(sk); ++ } + +- /* reset tx state */ +- so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + + return HRTIMER_NORESTART; + } + ++/* so->txtimer: fires when a Flow Control frame does not arrive in time */ ++static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ txtimer); ++ ++ return isotp_tx_timeout(so); ++} ++ ++/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */ ++static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ echotimer); ++ ++ return isotp_tx_timeout(so); ++} ++ + static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) + { + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txfrtimer); + + /* start echo timeout handling and cover below protocol error */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + + /* cfecho should be consumed by isotp_rcv_echo() here */ +@@ -920,13 +984,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; + s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; ++ struct hrtimer *tx_hrt = &so->echotimer; ++ u32 new_state = ISOTP_SENDING; + int off; + int err; + + if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) + return -EADDRNOTAVAIL; + +- while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) { ++ /* claim the socket under so->rx_lock: this serializes the claim ++ * with the RX path and with sendmsg()'s own error paths below, so ++ * none of them can ever see a transfer mid-claim ++ */ ++ for (;;) { ++ spin_lock_bh(&so->rx_lock); ++ if (READ_ONCE(so->tx.state) == ISOTP_IDLE) ++ break; ++ spin_unlock_bh(&so->rx_lock); ++ + /* we do not support multiple buffers - for now */ + if (msg->msg_flags & MSG_DONTWAIT) + return -EAGAIN; +@@ -935,11 +1010,23 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + return -EADDRNOTAVAIL; + + /* wait for complete transmission of current pdu */ +- err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); ++ err = wait_event_interruptible(so->wait, ++ so->tx.state == ISOTP_IDLE); + if (err) +- goto err_event_drop; ++ return err; + } + ++ /* new transfer: bump so->tx_gen and drain the old one's timers, ++ * still under the so->rx_lock we just claimed the socket with ++ */ ++ WRITE_ONCE(so->tx.state, ISOTP_SENDING); ++ WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); ++ hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); ++ hrtimer_cancel(&so->txfrtimer); ++ so->cfecho = 0; ++ spin_unlock_bh(&so->rx_lock); ++ + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ +@@ -1040,18 +1127,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + so->cfecho = *(u32 *)cf->data; + } else { + /* standard flow control check */ +- so->tx.state = ISOTP_WAIT_FIRST_FC; ++ new_state = ISOTP_WAIT_FIRST_FC; + + /* start timeout for FC */ + hrtimer_sec = ISOTP_FC_TIMEOUT; ++ tx_hrt = &so->txtimer; + + /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ + so->cfecho = 0; + } + } + +- hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0), ++ spin_lock_bh(&so->rx_lock); ++ if (so->tx.state == ISOTP_SHUTDOWN) { ++ /* isotp_release() has since taken over and already drained ++ * our timers - don't send into a socket that's going away ++ */ ++ spin_unlock_bh(&so->rx_lock); ++ kfree_skb(skb); ++ dev_put(dev); ++ wake_up_interruptible(&so->wait); ++ return -EADDRNOTAVAIL; ++ } ++ /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ ++ so->tx.state = new_state; ++ hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), + HRTIMER_MODE_REL_SOFT); ++ spin_unlock_bh(&so->rx_lock); + + /* send the first or only CAN frame */ + cf->flags = so->ll.tx_flags; +@@ -1064,13 +1166,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + pr_notice_once("can-isotp: %s: can_send_ret %pe\n", + __func__, ERR_PTR(err)); + ++ spin_lock_bh(&so->rx_lock); + /* no transmission -> no timeout monitoring */ +- hrtimer_cancel(&so->txtimer); +- +- /* reset consecutive frame echo tag */ +- so->cfecho = 0; +- +- goto err_out_drop; ++ hrtimer_cancel(tx_hrt); ++ goto err_out_drop_locked; + } + + if (wait_tx_done) { +@@ -1086,14 +1185,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + + return size; + ++err_out_drop: ++ /* claimed but nothing sent yet - no timer to cancel */ ++ spin_lock_bh(&so->rx_lock); ++ goto err_out_drop_locked; + err_event_drop: +- /* got signal: force tx state machine to be idle */ +- so->tx.state = ISOTP_IDLE; ++ /* interrupted waiting on our own transfer - drain its timers */ ++ spin_lock_bh(&so->rx_lock); + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); +-err_out_drop: +- /* drop this PDU and unlock a potential wait queue */ ++ hrtimer_cancel(&so->echotimer); ++err_out_drop_locked: ++ /* release the claim; so->rx_lock still held from above */ ++ so->cfecho = 0; + so->tx.state = ISOTP_IDLE; ++ spin_unlock_bh(&so->rx_lock); + wake_up_interruptible(&so->wait); + + return err; +@@ -1157,13 +1263,20 @@ static int isotp_release(struct socket *sock) + so = isotp_sk(sk); + net = sock_net(sk); + +- /* wait for complete transmission of current pdu */ +- while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 && +- cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE) ++ /* best-effort: wait for a running pdu to finish, but don't block on ++ * it forever - give up after the first signal ++ */ ++ while (so->tx.state != ISOTP_IDLE && ++ wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) + ; + +- /* force state machines to be idle also when a signal occurred */ ++ /* claim the socket under so->rx_lock like sendmsg() does, so its ++ * claim can't race the forced ISOTP_SHUTDOWN below; force it ++ * unconditionally, even when a signal cut the wait above short ++ */ ++ spin_lock_bh(&so->rx_lock); + so->tx.state = ISOTP_SHUTDOWN; ++ spin_unlock_bh(&so->rx_lock); + so->rx.state = ISOTP_IDLE; + + spin_lock(&isotp_notifier_lock); +@@ -1209,6 +1322,7 @@ static int isotp_release(struct socket *sock) + + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + hrtimer_cancel(&so->rxtimer); + + sock_orphan(sk); +@@ -1631,6 +1745,8 @@ static int isotp_init(struct sock *sk) + so->rxtimer.function = isotp_rx_timer_handler; + hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txtimer.function = isotp_tx_timer_handler; ++ hrtimer_init(&so->echotimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ so->echotimer.function = isotp_echo_timer_handler; + hrtimer_init(&so->txfrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txfrtimer.function = isotp_txfr_timer_handler; + +-- +2.53.0 + diff --git a/queue-5.10/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch b/queue-5.10/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch new file mode 100644 index 0000000000..ae3586da48 --- /dev/null +++ b/queue-5.10/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch @@ -0,0 +1,114 @@ +From e77c83e15c194e2f9168fe2002d8270273b0b6ad Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 8 Apr 2026 09:03:59 -0700 +Subject: Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data() + +From: Seungjin Bae + +[ Upstream commit 875115b82c295277b81b6dfee7debc725f44e854 ] + +The `ims_pcu_process_data()` processes incoming URB data byte by byte. +However, it fails to check if the `read_pos` index exceeds +IMS_PCU_BUF_SIZE. + +If a malicious USB device sends a packet larger than IMS_PCU_BUF_SIZE, +`read_pos` will increment indefinitely. Moreover, since `read_pos` is +located immediately after `read_buf`, the attacker can overwrite +`read_pos` itself to arbitrarily control the index. + +This manipulated `read_pos` is subsequently used in +`ims_pcu_handle_response()` to copy data into `cmd_buf`, leading to a +heap buffer overflow. + +Specifically, an attacker can overwrite the `cmd_done.wait.head` located +at offset 136 relative to `cmd_buf` in the `ims_pcu_handle_response()`. +Consequently, when the driver calls `complete(&pcu->cmd_done)`, it +triggers a control flow hijack by using the manipulated pointer. + +Fix this by adding a bounds check for `read_pos` before writing to +`read_buf`. If the packet is too long, discard it, log a warning, +and reset the parser state. + +Fixes: 628329d524743 ("Input: add IMS Passenger Control Unit driver") +Co-developed-by: Sanghoon Choi +Signed-off-by: Sanghoon Choi +Signed-off-by: Seungjin Bae +Link: https://patch.msgid.link/20251221211442.841549-2-eeodqql09@gmail.com +[dtor: factor out resetting packet state, reset checksum as well] +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 32 ++++++++++++++++++++++++++------ + 1 file changed, 26 insertions(+), 6 deletions(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 55d4fffda41bf2..71d96e7d842dab 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -448,6 +448,14 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + } + } + ++static void ims_pcu_reset_packet(struct ims_pcu *pcu) ++{ ++ pcu->have_stx = true; ++ pcu->have_dle = false; ++ pcu->read_pos = 0; ++ pcu->check_sum = 0; ++} ++ + static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + { + int i; +@@ -460,6 +468,14 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + continue; + + if (pcu->have_dle) { ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->have_dle = false; + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; +@@ -472,10 +488,8 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + dev_warn(pcu->dev, + "Unexpected STX at byte %d, discarding old data\n", + pcu->read_pos); ++ ims_pcu_reset_packet(pcu); + pcu->have_stx = true; +- pcu->have_dle = false; +- pcu->read_pos = 0; +- pcu->check_sum = 0; + break; + + case IMS_PCU_PROTOCOL_DLE: +@@ -495,12 +509,18 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + ims_pcu_handle_response(pcu); + } + +- pcu->have_stx = false; +- pcu->have_dle = false; +- pcu->read_pos = 0; ++ ims_pcu_reset_packet(pcu); + break; + + default: ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + break; +-- +2.53.0 + diff --git a/queue-5.10/input-ims-pcu-fix-logic-error-in-packet-reset.patch b/queue-5.10/input-ims-pcu-fix-logic-error-in-packet-reset.patch new file mode 100644 index 0000000000..597fc5c831 --- /dev/null +++ b/queue-5.10/input-ims-pcu-fix-logic-error-in-packet-reset.patch @@ -0,0 +1,42 @@ +From 62fd485d1b40603dae67943051702d875911ea97 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 22 May 2026 10:29:41 -0700 +Subject: Input: ims-pcu - fix logic error in packet reset + +From: Dmitry Torokhov + +[ Upstream commit 2c9b85a14abb4811e8d4773ccd13559e59792efb ] + +ims_pcu_reset_packet() incorrectly sets have_stx to true, which implies +that the start-of-packet delimiter has already been received. This +causes the protocol parser to skip waiting for the next STX byte and +potentially process garbage data. + +Correctly set have_stx to false when resetting the packet state. + +Fixes: 875115b82c29 ("Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data()") +Cc: stable@vger.kernel.org +Reported-by: Sashiko bot +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 71d96e7d842dab..0214c3bb2b096d 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -450,7 +450,7 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + + static void ims_pcu_reset_packet(struct ims_pcu *pcu) + { +- pcu->have_stx = true; ++ pcu->have_stx = false; + pcu->have_dle = false; + pcu->read_pos = 0; + pcu->check_sum = 0; +-- +2.53.0 + diff --git a/queue-5.10/series b/queue-5.10/series index ef2f5b1b1f..c4a8a5edea 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -3,3 +3,7 @@ cpu-hotplug-bound-hotplug-states-sysfs-output.patch macsec-don-t-read-an-unset-mac-header-in-macsec_encr.patch kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch kvm-x86-mmu-fix-use-after-free-on-vendor-module-reload.patch +can-isotp-fix-use-after-free-race-with-concurrent-ne.patch +can-isotp-serialize-tx-state-transitions-under-so-rx.patch +input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch +input-ims-pcu-fix-logic-error-in-packet-reset.patch diff --git a/queue-5.15/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch b/queue-5.15/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch new file mode 100644 index 0000000000..3b929c5ab7 --- /dev/null +++ b/queue-5.15/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch @@ -0,0 +1,238 @@ +From afb9618476c4dcb9b28dc35aea2e2283486c7223 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:32:49 +0200 +Subject: can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER + +From: Oliver Hartkopp + +commit 20bab8b88baac140ca3701116e1d486c7f51e311 upstream. + +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|rx].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 +Closes: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-2-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 88 +++++++++++++++++++++++++++++++++---------------- + 1 file changed, 59 insertions(+), 29 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index 813134f549f6c1..ee63f0de7ff543 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -137,6 +137,7 @@ struct isotp_sock { + struct sock sk; + int bound; + int ifindex; ++ struct net_device *dev; + canid_t txid; + canid_t rxid; + ktime_t tx_gap; +@@ -939,6 +940,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + 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; ++ } ++ + if (!size || size > MAX_MSG_LENGTH) { + err = -EINVAL; + goto err_out_drop; +@@ -1166,28 +1175,30 @@ static int isotp_release(struct socket *sock) + 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); ++ dev_put(so->dev); + } + ++ 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 +@@ -1200,9 +1211,6 @@ static int isotp_release(struct socket *sock) + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->rxtimer); + +- so->ifindex = 0; +- so->bound = 0; +- + sock_orphan(sk); + sock->sk = NULL; + +@@ -1256,6 +1264,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + if (!addr->can_ifindex) + return -ENODEV; + ++ rtnl_lock(); + lock_sock(sk); + + if (so->bound) { +@@ -1263,6 +1272,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + ++ /* A transmission or reception that outlived a previous binding ++ * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo ++ * and RX 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 || so->rx.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; +@@ -1275,14 +1295,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + if (dev->type != ARPHRD_CAN) { +- dev_put(dev); + err = -ENODEV; +- goto out; ++ goto out_put_dev; + } +- if (dev->mtu < so->ll.mtu) { +- dev_put(dev); ++ if (READ_ONCE(dev->mtu) < so->ll.mtu) { + err = -EINVAL; +- goto out; ++ goto out_put_dev; + } + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; +@@ -1300,16 +1318,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + 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; ++ dev_hold(so->dev); ++ ++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; +@@ -1512,7 +1539,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + if (!net_eq(dev_net(dev), sock_net(sk))) + return; + +- if (so->ifindex != dev->ifindex) ++ if (so->dev != dev) + return; + + switch (msg) { +@@ -1528,10 +1555,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + can_rx_unregister(dev_net(dev), dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); ++ dev_put(so->dev); + } + + so->ifindex = 0; + so->bound = 0; ++ so->dev = NULL; + release_sock(sk); + + sk->sk_err = ENODEV; +@@ -1576,6 +1605,7 @@ static int isotp_init(struct sock *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; +-- +2.53.0 + diff --git a/queue-5.15/can-isotp-serialize-tx-state-transitions-under-so-rx.patch b/queue-5.15/can-isotp-serialize-tx-state-transitions-under-so-rx.patch new file mode 100644 index 0000000000..9050fb5324 --- /dev/null +++ b/queue-5.15/can-isotp-serialize-tx-state-transitions-under-so-rx.patch @@ -0,0 +1,426 @@ +From 6ca343a2e610dfe9037acae9f404e3f94c9d3985 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:32:50 +0200 +Subject: can: isotp: serialize TX state transitions under so->rx_lock + +From: Oliver Hartkopp + +commit cf070fe33bfbd1a4c21236078fadb35dd223a157 upstream. + +The TX state machine (so->tx.state) is driven from three contexts: +sendmsg() claiming and progressing a transfer, the RX path consuming +Flow Control/echo frames, and two hrtimers timing out a stalled +transfer. Mixing a lock-free cmpxchg() claim in sendmsg() with +hrtimer_cancel() calls made under so->rx_lock elsewhere left windows +where a frame or timer callback could act on a state that had already +moved on, corrupting an unrelated transfer. + +so->rx_lock now covers the full lifecycle of a TX claim: sendmsg() +takes it to check so->tx.state is ISOTP_IDLE, switch it to +ISOTP_SENDING, bump so->tx_gen and drain the previous transfer's +timers - all as one critical section. isotp_rcv_fc()/isotp_rcv_cf() +already run under this lock via isotp_rcv(), and isotp_rcv_echo() now +takes it itself, so none of them can ever observe a transfer mid-claim. +This also means a transfer can no longer be handed to sendmsg()'s +cleanup paths (signal or send error) while another thread is +concurrently claiming or finishing it, so those paths can cancel +timers and reset the state unconditionally. + +isotp_release() claims the socket the same way, so a racing sendmsg() +sees a consistent ISOTP_SHUTDOWN and skips arming its timer or sending. + +Only the hrtimer callbacks stay outside so->rx_lock, since they run +under so->rx_lock's cancellation elsewhere and taking it themselves +would deadlock. so->tx_gen lets them recognize whether the transfer +they timed out is still the one currently active, so they don't +report an error against a transfer that has since completed or been +superseded. + +Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-3-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 192 ++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 154 insertions(+), 38 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index ee63f0de7ff543..de6992580c38cb 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -142,7 +142,7 @@ struct isotp_sock { + canid_t rxid; + ktime_t tx_gap; + ktime_t lastrxcf_tstamp; +- struct hrtimer rxtimer, txtimer, txfrtimer; ++ struct hrtimer rxtimer, txtimer, txfrtimer, echotimer; + struct can_isotp_options opt; + struct can_isotp_fc_options rxfc, txfc; + struct can_isotp_ll_options ll; +@@ -150,6 +150,7 @@ struct isotp_sock { + u32 force_tx_stmin; + u32 force_rx_stmin; + u32 cfecho; /* consecutive frame echo tag */ ++ u32 tx_gen; /* generation, bumped per new tx transfer */ + struct tpcon rx, tx; + struct list_head notifier; + wait_queue_head_t wait; +@@ -356,6 +357,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + + hrtimer_cancel(&so->txtimer); + ++ /* isotp_tx_timeout() may have given up on this job while ++ * hrtimer_cancel() above waited for it to finish; so->rx_lock ++ * (held by our caller isotp_rcv()) rules out a concurrent claim, ++ * so a plain recheck is enough here. ++ */ ++ if (so->tx.state != ISOTP_WAIT_FC && ++ so->tx.state != ISOTP_WAIT_FIRST_FC) ++ return 1; ++ + if ((cf->len < ae + FC_CONTENT_SZ) || + ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { +@@ -401,7 +411,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + so->tx.bs = 0; + so->tx.state = ISOTP_SENDING; + /* send CF frame and enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); + break; +@@ -544,6 +554,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; ++ + /* CFs are never longer than the FF */ + if (cf->len > so->rx.ll_dl) + return 1; +@@ -833,20 +851,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + + /* only handle my own local echo CF/SF skb's (no FF!) */ +- if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) ++ if (skb->sk != sk) + return; + ++ /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock ++ * (no isotp_rcv() caller here), so take it ourselves ++ */ ++ spin_lock(&so->rx_lock); ++ ++ /* so->cfecho may since belong to a new transfer; recheck under lock */ ++ if (so->cfecho != *(u32 *)cf->data) ++ goto out_unlock; ++ + /* cancel local echo timeout */ +- hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + + /* local echo skb with consecutive frame has been consumed */ + so->cfecho = 0; + ++ /* claiming a transfer also takes so->rx_lock, so a plain recheck ++ * is enough: so->tx.state can't have flipped to ISOTP_SENDING for ++ * a new claim while we're still in here ++ */ ++ if (so->tx.state != ISOTP_SENDING) ++ goto out_unlock; ++ + if (so->tx.idx >= so->tx.len) { + /* we are done */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); +- return; ++ goto out_unlock; + } + + if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { +@@ -854,53 +888,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + so->tx.state = ISOTP_WAIT_FC; + hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); +- return; ++ goto out_unlock; + } + + /* no gap between data frames needed => use burst mode */ + if (!so->tx_gap) { + /* enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); +- return; ++ goto out_unlock; + } + + /* start timer to send next consecutive frame with correct delay */ + hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT); ++ ++out_unlock: ++ spin_unlock(&so->rx_lock); + } + +-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get ++ * cancelled under so->rx_lock elsewhere, so this must stay lock-free to ++ * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting ++ * a new transfer with an error from the one that just timed out. ++ */ ++static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) + { +- struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, +- txtimer); + struct sock *sk = &so->sk; ++ u32 gen = READ_ONCE(so->tx_gen); ++ u32 old_state = READ_ONCE(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 the timeout if the state is still unchanged */ ++ 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); ++ if (READ_ONCE(so->tx_gen) == gen) { ++ /* 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; + } + ++/* so->txtimer: fires when a Flow Control frame does not arrive in time */ ++static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ txtimer); ++ ++ return isotp_tx_timeout(so); ++} ++ ++/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */ ++static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ echotimer); ++ ++ return isotp_tx_timeout(so); ++} ++ + static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) + { + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txfrtimer); + + /* start echo timeout handling and cover below protocol error */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + + /* cfecho should be consumed by isotp_rcv_echo() here */ +@@ -920,13 +984,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; + s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; ++ struct hrtimer *tx_hrt = &so->echotimer; ++ u32 new_state = ISOTP_SENDING; + int off; + int err; + + if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) + return -EADDRNOTAVAIL; + +- while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) { ++ /* claim the socket under so->rx_lock: this serializes the claim ++ * with the RX path and with sendmsg()'s own error paths below, so ++ * none of them can ever see a transfer mid-claim ++ */ ++ for (;;) { ++ spin_lock_bh(&so->rx_lock); ++ if (READ_ONCE(so->tx.state) == ISOTP_IDLE) ++ break; ++ spin_unlock_bh(&so->rx_lock); ++ + /* we do not support multiple buffers - for now */ + if (msg->msg_flags & MSG_DONTWAIT) + return -EAGAIN; +@@ -935,11 +1010,23 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + return -EADDRNOTAVAIL; + + /* wait for complete transmission of current pdu */ +- err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); ++ err = wait_event_interruptible(so->wait, ++ so->tx.state == ISOTP_IDLE); + if (err) +- goto err_event_drop; ++ return err; + } + ++ /* new transfer: bump so->tx_gen and drain the old one's timers, ++ * still under the so->rx_lock we just claimed the socket with ++ */ ++ WRITE_ONCE(so->tx.state, ISOTP_SENDING); ++ WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); ++ hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); ++ hrtimer_cancel(&so->txfrtimer); ++ so->cfecho = 0; ++ spin_unlock_bh(&so->rx_lock); ++ + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ +@@ -1040,18 +1127,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + so->cfecho = *(u32 *)cf->data; + } else { + /* standard flow control check */ +- so->tx.state = ISOTP_WAIT_FIRST_FC; ++ new_state = ISOTP_WAIT_FIRST_FC; + + /* start timeout for FC */ + hrtimer_sec = ISOTP_FC_TIMEOUT; ++ tx_hrt = &so->txtimer; + + /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ + so->cfecho = 0; + } + } + +- hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0), ++ spin_lock_bh(&so->rx_lock); ++ if (so->tx.state == ISOTP_SHUTDOWN) { ++ /* isotp_release() has since taken over and already drained ++ * our timers - don't send into a socket that's going away ++ */ ++ spin_unlock_bh(&so->rx_lock); ++ kfree_skb(skb); ++ dev_put(dev); ++ wake_up_interruptible(&so->wait); ++ return -EADDRNOTAVAIL; ++ } ++ /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ ++ so->tx.state = new_state; ++ hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), + HRTIMER_MODE_REL_SOFT); ++ spin_unlock_bh(&so->rx_lock); + + /* send the first or only CAN frame */ + cf->flags = so->ll.tx_flags; +@@ -1064,13 +1166,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + pr_notice_once("can-isotp: %s: can_send_ret %pe\n", + __func__, ERR_PTR(err)); + ++ spin_lock_bh(&so->rx_lock); + /* no transmission -> no timeout monitoring */ +- hrtimer_cancel(&so->txtimer); +- +- /* reset consecutive frame echo tag */ +- so->cfecho = 0; +- +- goto err_out_drop; ++ hrtimer_cancel(tx_hrt); ++ goto err_out_drop_locked; + } + + if (wait_tx_done) { +@@ -1086,14 +1185,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + + return size; + ++err_out_drop: ++ /* claimed but nothing sent yet - no timer to cancel */ ++ spin_lock_bh(&so->rx_lock); ++ goto err_out_drop_locked; + err_event_drop: +- /* got signal: force tx state machine to be idle */ +- so->tx.state = ISOTP_IDLE; ++ /* interrupted waiting on our own transfer - drain its timers */ ++ spin_lock_bh(&so->rx_lock); + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); +-err_out_drop: +- /* drop this PDU and unlock a potential wait queue */ ++ hrtimer_cancel(&so->echotimer); ++err_out_drop_locked: ++ /* release the claim; so->rx_lock still held from above */ ++ so->cfecho = 0; + so->tx.state = ISOTP_IDLE; ++ spin_unlock_bh(&so->rx_lock); + wake_up_interruptible(&so->wait); + + return err; +@@ -1157,13 +1263,20 @@ static int isotp_release(struct socket *sock) + so = isotp_sk(sk); + net = sock_net(sk); + +- /* wait for complete transmission of current pdu */ +- while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 && +- cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE) ++ /* best-effort: wait for a running pdu to finish, but don't block on ++ * it forever - give up after the first signal ++ */ ++ while (so->tx.state != ISOTP_IDLE && ++ wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) + ; + +- /* force state machines to be idle also when a signal occurred */ ++ /* claim the socket under so->rx_lock like sendmsg() does, so its ++ * claim can't race the forced ISOTP_SHUTDOWN below; force it ++ * unconditionally, even when a signal cut the wait above short ++ */ ++ spin_lock_bh(&so->rx_lock); + so->tx.state = ISOTP_SHUTDOWN; ++ spin_unlock_bh(&so->rx_lock); + so->rx.state = ISOTP_IDLE; + + spin_lock(&isotp_notifier_lock); +@@ -1209,6 +1322,7 @@ static int isotp_release(struct socket *sock) + + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + hrtimer_cancel(&so->rxtimer); + + sock_orphan(sk); +@@ -1631,6 +1745,8 @@ static int isotp_init(struct sock *sk) + so->rxtimer.function = isotp_rx_timer_handler; + hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txtimer.function = isotp_tx_timer_handler; ++ hrtimer_init(&so->echotimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ so->echotimer.function = isotp_echo_timer_handler; + hrtimer_init(&so->txfrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txfrtimer.function = isotp_txfr_timer_handler; + +-- +2.53.0 + diff --git a/queue-5.15/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch b/queue-5.15/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch new file mode 100644 index 0000000000..ca9bcd4719 --- /dev/null +++ b/queue-5.15/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch @@ -0,0 +1,198 @@ +From 299771156976f76a15b7dacb2829b8eb3557b84a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 14:55:37 +0200 +Subject: dmaengine: sh: rz-dmac: Move interrupt request after everything is + set up + +From: Claudiu Beznea + +commit 731712403ddb39d1a76a11abf339a0615bc85de7 upstream. + +Once the interrupt is requested, the interrupt handler may run immediately. +Since the IRQ handler can access channel->ch_base, which is initialized +only after requesting the IRQ, this may lead to invalid memory access. +Likewise, the IRQ thread may access uninitialized data (the ld_free, +ld_queue, and ld_active lists), which may also lead to issues. + +Request the interrupts only after everything is set up. To keep the error +path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent(). + +Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC") +Cc: stable@vger.kernel.org +Reviewed-by: Frank Li +Tested-by: John Madieu +Signed-off-by: Claudiu Beznea +Tested-by: Tommaso Merciai +Link: https://patch.msgid.link/20260526084710.3491480-2-claudiu.beznea@kernel.org +Signed-off-by: Vinod Koul +[tm: kept channel->irq (a struct field, not a local irq variable) and + the sprintf()/pdev_irqname[5] pair in rz_dmac_chan_probe(), and the + mandatory platform_get_irq_byname() call for the error IRQ in + rz_dmac_probe(), since e0c51fd02f9c ("Make channel irq local"), + c4d6dcb3b625 ("Avoid format-overflow warning") and b34f3fcae72a + ("make error interrupt optional") are not yet backported to this + tree] +Signed-off-by: Tommaso Merciai +Signed-off-by: Sasha Levin +--- + drivers/dma/sh/rz-dmac.c | 94 ++++++++++++++++------------------------ + 1 file changed, 37 insertions(+), 57 deletions(-) + +diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c +index c22743e41640fd..cec94db38c550e 100644 +--- a/drivers/dma/sh/rz-dmac.c ++++ b/drivers/dma/sh/rz-dmac.c +@@ -773,27 +773,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + channel->index = index; + channel->mid_rid = -EINVAL; + +- /* Request the channel interrupt. */ +- sprintf(pdev_irqname, "ch%u", index); +- channel->irq = platform_get_irq_byname(pdev, pdev_irqname); +- if (channel->irq < 0) +- return channel->irq; +- +- irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", +- dev_name(dmac->dev), index); +- if (!irqname) +- return -ENOMEM; +- +- ret = devm_request_threaded_irq(dmac->dev, channel->irq, +- rz_dmac_irq_handler, +- rz_dmac_irq_handler_thread, 0, +- irqname, channel); +- if (ret) { +- dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", +- channel->irq, ret); +- return ret; +- } +- + /* Set io base address for each channel */ + if (index < 8) { + channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET + +@@ -806,9 +785,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + } + + /* Allocate descriptors */ +- lmdesc = dma_alloc_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- &channel->lmdesc.base_dma, GFP_KERNEL); ++ lmdesc = dmam_alloc_coherent(&pdev->dev, ++ sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, ++ &channel->lmdesc.base_dma, GFP_KERNEL); + if (!lmdesc) { + dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n"); + return -ENOMEM; +@@ -824,7 +803,26 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + INIT_LIST_HEAD(&channel->ld_free); + INIT_LIST_HEAD(&channel->ld_active); + +- return 0; ++ /* Request the channel interrupt. */ ++ sprintf(pdev_irqname, "ch%u", index); ++ channel->irq = platform_get_irq_byname(pdev, pdev_irqname); ++ if (channel->irq < 0) ++ return channel->irq; ++ ++ irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", ++ dev_name(dmac->dev), index); ++ if (!irqname) ++ return -ENOMEM; ++ ++ ret = devm_request_threaded_irq(dmac->dev, channel->irq, ++ rz_dmac_irq_handler, ++ rz_dmac_irq_handler_thread, 0, ++ irqname, channel); ++ if (ret) ++ dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", ++ channel->irq, ret); ++ ++ return ret; + } + + static int rz_dmac_parse_of(struct device *dev, struct rz_dmac *dmac) +@@ -851,7 +849,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + const char *irqname = "error"; + struct dma_device *engine; + struct rz_dmac *dmac; +- int channel_num; + unsigned int i; + int ret; + int irq; +@@ -881,26 +878,28 @@ static int rz_dmac_probe(struct platform_device *pdev) + if (IS_ERR(dmac->ext_base)) + return PTR_ERR(dmac->ext_base); + ++ /* Initialize the channels. */ ++ INIT_LIST_HEAD(&dmac->engine.channels); ++ ++ for (i = 0; i < dmac->n_channels; i++) { ++ ret = rz_dmac_chan_probe(dmac, &dmac->channels[i], i); ++ if (ret < 0) ++ goto err; ++ } ++ + /* Register interrupt handler for error */ + irq = platform_get_irq_byname(pdev, irqname); +- if (irq < 0) +- return irq; ++ if (irq < 0) { ++ ret = irq; ++ goto err; ++ } + + ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, + irqname, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", + irq, ret); +- return ret; +- } +- +- /* Initialize the channels. */ +- INIT_LIST_HEAD(&dmac->engine.channels); +- +- for (i = 0; i < dmac->n_channels; i++) { +- ret = rz_dmac_chan_probe(dmac, &dmac->channels[i], i); +- if (ret < 0) +- goto err; ++ goto err; + } + + /* Register the DMAC as a DMA provider for DT. */ +@@ -940,32 +939,13 @@ static int rz_dmac_probe(struct platform_device *pdev) + dma_register_err: + of_dma_controller_free(pdev->dev.of_node); + err: +- channel_num = i ? i - 1 : 0; +- for (i = 0; i < channel_num; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } +- + return ret; + } + + static int rz_dmac_remove(struct platform_device *pdev) + { + struct rz_dmac *dmac = platform_get_drvdata(pdev); +- unsigned int i; +- +- for (i = 0; i < dmac->n_channels; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; + +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&dmac->engine); + +-- +2.53.0 + diff --git a/queue-5.15/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch b/queue-5.15/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch new file mode 100644 index 0000000000..a247a62554 --- /dev/null +++ b/queue-5.15/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch @@ -0,0 +1,114 @@ +From 3ef0a64dbf7ab736493532d4ec8693939553f035 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 8 Apr 2026 09:03:59 -0700 +Subject: Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data() + +From: Seungjin Bae + +[ Upstream commit 875115b82c295277b81b6dfee7debc725f44e854 ] + +The `ims_pcu_process_data()` processes incoming URB data byte by byte. +However, it fails to check if the `read_pos` index exceeds +IMS_PCU_BUF_SIZE. + +If a malicious USB device sends a packet larger than IMS_PCU_BUF_SIZE, +`read_pos` will increment indefinitely. Moreover, since `read_pos` is +located immediately after `read_buf`, the attacker can overwrite +`read_pos` itself to arbitrarily control the index. + +This manipulated `read_pos` is subsequently used in +`ims_pcu_handle_response()` to copy data into `cmd_buf`, leading to a +heap buffer overflow. + +Specifically, an attacker can overwrite the `cmd_done.wait.head` located +at offset 136 relative to `cmd_buf` in the `ims_pcu_handle_response()`. +Consequently, when the driver calls `complete(&pcu->cmd_done)`, it +triggers a control flow hijack by using the manipulated pointer. + +Fix this by adding a bounds check for `read_pos` before writing to +`read_buf`. If the packet is too long, discard it, log a warning, +and reset the parser state. + +Fixes: 628329d524743 ("Input: add IMS Passenger Control Unit driver") +Co-developed-by: Sanghoon Choi +Signed-off-by: Sanghoon Choi +Signed-off-by: Seungjin Bae +Link: https://patch.msgid.link/20251221211442.841549-2-eeodqql09@gmail.com +[dtor: factor out resetting packet state, reset checksum as well] +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 32 ++++++++++++++++++++++++++------ + 1 file changed, 26 insertions(+), 6 deletions(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 59055818e0e6fc..b4e7dd8a993737 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -448,6 +448,14 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + } + } + ++static void ims_pcu_reset_packet(struct ims_pcu *pcu) ++{ ++ pcu->have_stx = true; ++ pcu->have_dle = false; ++ pcu->read_pos = 0; ++ pcu->check_sum = 0; ++} ++ + static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + { + int i; +@@ -460,6 +468,14 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + continue; + + if (pcu->have_dle) { ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->have_dle = false; + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; +@@ -472,10 +488,8 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + dev_warn(pcu->dev, + "Unexpected STX at byte %d, discarding old data\n", + pcu->read_pos); ++ ims_pcu_reset_packet(pcu); + pcu->have_stx = true; +- pcu->have_dle = false; +- pcu->read_pos = 0; +- pcu->check_sum = 0; + break; + + case IMS_PCU_PROTOCOL_DLE: +@@ -495,12 +509,18 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + ims_pcu_handle_response(pcu); + } + +- pcu->have_stx = false; +- pcu->have_dle = false; +- pcu->read_pos = 0; ++ ims_pcu_reset_packet(pcu); + break; + + default: ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + break; +-- +2.53.0 + diff --git a/queue-5.15/input-ims-pcu-fix-logic-error-in-packet-reset.patch b/queue-5.15/input-ims-pcu-fix-logic-error-in-packet-reset.patch new file mode 100644 index 0000000000..8ea95dcf23 --- /dev/null +++ b/queue-5.15/input-ims-pcu-fix-logic-error-in-packet-reset.patch @@ -0,0 +1,42 @@ +From f14ab5e1e9d3b5be7e37f5f94fcbe63f2af0590c Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 22 May 2026 10:29:41 -0700 +Subject: Input: ims-pcu - fix logic error in packet reset + +From: Dmitry Torokhov + +[ Upstream commit 2c9b85a14abb4811e8d4773ccd13559e59792efb ] + +ims_pcu_reset_packet() incorrectly sets have_stx to true, which implies +that the start-of-packet delimiter has already been received. This +causes the protocol parser to skip waiting for the next STX byte and +potentially process garbage data. + +Correctly set have_stx to false when resetting the packet state. + +Fixes: 875115b82c29 ("Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data()") +Cc: stable@vger.kernel.org +Reported-by: Sashiko bot +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index b4e7dd8a993737..6b1d8f92a9bb06 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -450,7 +450,7 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + + static void ims_pcu_reset_packet(struct ims_pcu *pcu) + { +- pcu->have_stx = true; ++ pcu->have_stx = false; + pcu->have_dle = false; + pcu->read_pos = 0; + pcu->check_sum = 0; +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index 2f0538a201..18bca0a39a 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -3,3 +3,8 @@ futex-prevent-lockup-in-requeue-pi-during-signal-tim.patch macsec-don-t-read-an-unset-mac-header-in-macsec_encr.patch kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch kvm-x86-mmu-fix-use-after-free-on-vendor-module-reload.patch +can-isotp-fix-use-after-free-race-with-concurrent-ne.patch +can-isotp-serialize-tx-state-transitions-under-so-rx.patch +dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch +input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch +input-ims-pcu-fix-logic-error-in-packet-reset.patch diff --git a/queue-6.1/can-bcm-add-locking-when-updating-filter-and-timer-v.patch b/queue-6.1/can-bcm-add-locking-when-updating-filter-and-timer-v.patch new file mode 100644 index 0000000000..ccd3577207 --- /dev/null +++ b/queue-6.1/can-bcm-add-locking-when-updating-filter-and-timer-v.patch @@ -0,0 +1,412 @@ +From a8d3d63294bf736b4ed677704ea2233d3c179158 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:22 +0200 +Subject: can: bcm: add locking when updating filter and timer values + +From: Oliver Hartkopp + +commit 749179c2e25b95d22499ed29096b3e02d6dfd2b4 upstream. + +KCSAN detected a simultaneous access to timer values that can be +overwritten in bcm_rx_setup() when updating timer and filter content +while bcm_rx_handler(), bcm_rx_timeout_handler() or bcm_rx_thr_handler() +run concurrently on incoming CAN traffic. + +Protect the timer (ival1/ival2/kt_ival1/kt_ival2/kt_lastmsg) and filter +(nframes/flags/frames/last_frames) updates in bcm_rx_setup() with a new +per-op bcm_rx_update_lock, taken with the matching scope in the RX +handlers. memcpy_from_msg() is staged into a temporary buffer before the +lock is taken, since it can sleep and must not run under a spinlock. + +hrtimer_cancel() is always called without bcm_rx_update_lock held, since +bcm_rx_timeout_handler()/bcm_rx_thr_handler() take the same lock and a +running callback would otherwise deadlock against the canceller. + +Also close a related race: bcm_rx_setup() cleared the RTR flag in the +stored reply frame's can_id as a separate, unprotected step after the +frame content was already installed, so a concurrent bcm_rx_handler() +could transmit a stale reply with CAN_RTR_FLAG still set. Fold that +normalization into the initial frame preparation instead (on the staged +buffer for updates, directly on op->frames pre-registration for new +ops), so the installed frame is always atomically self-consistent. + +bcm_rx_handler()'s RX_RTR_FRAME check now takes a lock-protected +snapshot of op->flags before deciding whether to call bcm_can_tx(), +but does not hold the lock across that call. + +Also take a lock-protected snapshot of the currframe in bcm_can_tx() +to avoid partly overwrites by content updates in bcm_tx_setup(). +Finally check if a TX_RESET_MULTI_IDX/SETTIMER might have reset +op->currframe between the two locked sections in bcm_can_tx(). + +Omit calling hrtimer_forward() with zero interval in bcm_rx_thr_handler(). +kt_ival2 may have been concurrently cleared by bcm_rx_setup() before it +cancels this timer, so check kt_ival2 inside the bcm_rx_update_lock. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Reported-by: syzbot+75e5e4ae00c3b4bb544e@syzkaller.appspotmail.com +Closes: https://lore.kernel.org/linux-can/6975d5cf.a00a0220.33ccc7.0022.GAE@google.com/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 176 ++++++++++++++++++++++++++++++++++++++------------ + 1 file changed, 133 insertions(+), 43 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 9fc733b54a86a0..5673cda3df2c62 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -126,6 +126,7 @@ struct bcm_op { + struct sock *sk; + struct net_device *rx_reg_dev; + spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + + struct bcm_sock { +@@ -280,21 +281,27 @@ static int bcm_proc_show(struct seq_file *m, void *v) + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op + */ +-static void bcm_can_tx(struct bcm_op *op) ++static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + { + struct sk_buff *skb; + struct net_device *dev; +- struct canfd_frame *cf; ++ struct canfd_frame cframe; ++ bool cyclic = !cf; ++ unsigned int idx = 0; + int err; + + /* no target device? => exit */ + if (!op->ifindex) + return; + +- /* read currframe under lock protection */ +- spin_lock_bh(&op->bcm_tx_lock); +- cf = op->frames + op->cfsiz * op->currframe; +- spin_unlock_bh(&op->bcm_tx_lock); ++ if (cyclic) { ++ /* read currframe under lock protection */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ idx = op->currframe; ++ memcpy(&cframe, op->frames + op->cfsiz * idx, op->cfsiz); ++ cf = &cframe; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ } + + dev = dev_get_by_index(sock_net(op->sk), op->ifindex); + if (!dev) { +@@ -323,14 +330,20 @@ static void bcm_can_tx(struct bcm_op *op) + if (!err) + op->frames_abs++; + +- op->currframe++; ++ /* only advance the cyclic sequence if nothing reset currframe while ++ * we were sending - a concurrent TX_RESET_MULTI_IDX means this ++ * frame's bookkeeping belongs to a sequence that no longer exists ++ */ ++ if (!cyclic || op->currframe == idx) { ++ op->currframe++; + +- /* reached last frame? */ +- if (op->currframe >= op->nframes) +- op->currframe = 0; ++ /* reached last frame? */ ++ if (op->currframe >= op->nframes) ++ op->currframe = 0; + +- if (op->count > 0) +- op->count--; ++ if (op->count > 0) ++ op->count--; ++ } + + spin_unlock_bh(&op->bcm_tx_lock); + out: +@@ -429,7 +442,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_msg_head msg_head; + + if (op->kt_ival1 && (op->count > 0)) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + if (!op->count && (op->flags & TX_COUNTEVT)) { + + /* create notification to user */ +@@ -446,7 +459,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + } + + } else if (op->kt_ival2) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + } + + return bcm_tx_set_expiry(op, &op->timer) ? +@@ -585,6 +598,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + /* if user wants to be informed, when cyclic CAN-Messages come back */ + if ((op->flags & RX_ANNOUNCE_RESUME) && op->last_frames) { + /* clear received CAN frames to indicate 'nothing received' */ +@@ -601,6 +616,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ + bcm_send_to_user(op, &msg_head, NULL, 0); + + return HRTIMER_NORESTART; +@@ -649,15 +666,26 @@ static int bcm_rx_thr_flush(struct bcm_op *op) + static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer); ++ enum hrtimer_restart ret; + +- if (bcm_rx_thr_flush(op)) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ /* kt_ival2 may have been concurrently cleared by bcm_rx_setup() ++ * before it cancels this timer - never forward with a zero ++ * interval in that case. ++ */ ++ if (bcm_rx_thr_flush(op) && op->kt_ival2) { + hrtimer_forward_now(hrtimer, op->kt_ival2); +- return HRTIMER_RESTART; ++ ret = HRTIMER_RESTART; + } else { + /* rearm throttle handling */ + op->kt_lastmsg = 0; +- return HRTIMER_NORESTART; ++ ret = HRTIMER_NORESTART; + } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ return ret; + } + + /* +@@ -667,7 +695,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + { + struct bcm_op *op = (struct bcm_op *)data; + const struct canfd_frame *rxframe = (struct canfd_frame *)skb->data; ++ struct canfd_frame rtrframe; + unsigned int i; ++ bool rtr_frame; + + if (op->can_id != rxframe->can_id) + return; +@@ -691,12 +721,23 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* update statistics */ + op->frames_abs++; + +- if (op->flags & RX_RTR_FRAME) { ++ /* snapshot the flag under lock: op->flags/op->frames may be updated ++ * concurrently by bcm_rx_setup(). ++ */ ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ rtr_frame = op->flags & RX_RTR_FRAME; ++ if (rtr_frame) ++ memcpy(&rtrframe, op->frames, op->cfsiz); ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ +- bcm_can_tx(op); ++ bcm_can_tx(op, &rtrframe); + return; + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe); +@@ -730,6 +771,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + + rx_starttimer: + bcm_rx_starttimer(op); ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + /* +@@ -1073,7 +1116,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + list_add_rcu(&op->list, &bo->tx_ops); + + if (op->flags & TX_ANNOUNCE) +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + + if (op->flags & STARTTIMER) + bcm_tx_start_timer(op); +@@ -1087,6 +1130,24 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + ++static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) ++{ ++ /* funny feature in RX(!)_SETUP only for RTR-mode: ++ * copy can_id into frame BUT without RTR-flag to ++ * prevent a full-load-loopback-test ... ;-] ++ * normalize this on the staged buffer, before it is ++ * ever installed into op->frames. ++ */ ++ if (msg_head->flags & RX_RTR_FRAME) { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; ++ } ++} ++ + /* + * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg) + */ +@@ -1121,6 +1182,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->rx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames = NULL; ++ + /* update existing BCM operation */ + + /* +@@ -1132,19 +1195,48 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -E2BIG; + + if (msg_head->nframes) { +- /* update CAN frames content */ +- err = memcpy_from_msg(op->frames, msg, ++ /* get new CAN frames content before locking */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, ++ GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ ++ err = memcpy_from_msg(new_frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + +- /* clear last_frames to indicate 'nothing received' */ +- memset(op->last_frames, 0, msg_head->nframes * op->cfsiz); ++ bcm_rx_setup_rtr_check(msg_head, op, new_frames); + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); + op->nframes = msg_head->nframes; + op->flags = msg_head->flags; + ++ if (msg_head->nframes) { ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, ++ msg_head->nframes * op->cfsiz); ++ ++ /* clear last_frames to indicate 'nothing received' */ ++ memset(op->last_frames, 0, ++ msg_head->nframes * op->cfsiz); ++ } ++ ++ if (msg_head->flags & SETTIMER) { ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ } ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ /* free temporary frames / kfree(NULL) is safe */ ++ kfree(new_frames); ++ + /* Only an update -> do not call can_rx_register() */ + do_rx_register = 0; + +@@ -1155,6 +1247,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -ENOMEM; + + spin_lock_init(&op->bcm_tx_lock); ++ spin_lock_init(&op->bcm_rx_update_lock); + op->can_id = msg_head->can_id; + op->nframes = msg_head->nframes; + op->cfsiz = CFSIZ(msg_head->flags); +@@ -1196,6 +1289,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + kfree(op); + return err; + } ++ ++ bcm_rx_setup_rtr_check(msg_head, op, op->frames); + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1223,29 +1318,22 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check flags */ + + if (op->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = op->frames; +- + /* no timers in RTR-mode */ + hrtimer_cancel(&op->thrtimer); + hrtimer_cancel(&op->timer); +- +- /* +- * funny feature in RX(!)_SETUP only for RTR-mode: +- * copy can_id into frame BUT without RTR-flag to +- * prevent a full-load-loopback-test ... ;-] +- */ +- if ((op->flags & TX_CP_CAN_ID) || +- (frame0->can_id == op->can_id)) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- + } else { + if (op->flags & SETTIMER) { + +- /* set timer value */ +- op->ival1 = msg_head->ival1; +- op->ival2 = msg_head->ival2; +- op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); +- op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ /* set timers (locked) for newly created op */ ++ if (do_rx_register) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } + + /* disable an active timer due to zero value? */ + if (!op->kt_ival1) +@@ -1255,9 +1343,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + * In any case cancel the throttle timer, flush + * potentially blocked msgs and reset throttle handling + */ +- op->kt_lastmsg = 0; + hrtimer_cancel(&op->thrtimer); ++ ++ spin_lock_bh(&op->bcm_rx_update_lock); + bcm_rx_thr_flush(op); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + if ((op->flags & STARTTIMER) && op->kt_ival1) +-- +2.53.0 + diff --git a/queue-6.1/can-bcm-add-missing-device-refcount-for-can-filter-r.patch b/queue-6.1/can-bcm-add-missing-device-refcount-for-can-filter-r.patch new file mode 100644 index 0000000000..2850d24c3f --- /dev/null +++ b/queue-6.1/can-bcm-add-missing-device-refcount-for-can-filter-r.patch @@ -0,0 +1,121 @@ +From 06ff2decc1e2601f7e72e2ba61800bfdc3d08542 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:26 +0200 +Subject: can: bcm: add missing device refcount for CAN filter removal + +From: Oliver Hartkopp + +commit d59948293ea34b6337ce2b5febab8510de70048c upstream. + +sashiko-bot remarked a problem with a concurrent device unregistration +in isotp.c which also is present in the bcm.c code. A former fix for raw.c +commit c275a176e4b6 ("can: raw: add missing refcount for memory leak fix") +introduced a netdevice_tracker which solves the issue for bcm.c too. + +bcm_release(), bcm_delete_rx_op() and bcm_notifier() relied on +dev_get_by_index(ifindex) to re-find the device for an rx_op before +unregistering its filter. If a concurrent NETDEV_UNREGISTER has already +unlisted the device from the ifindex table, that lookup fails and +can_rx_unregister() is silently skipped, leaving a stale CAN filter +pointing at the soon-to-be-freed bcm_op/socket. + +Hold a netdev_hold()/netdev_put() tracked reference on op->rx_reg_dev +from the moment the rx filter is registered in bcm_rx_setup() until it +is unregistered in bcm_rx_unreg(), and use that reference directly in +bcm_release() and bcm_delete_rx_op() instead of re-looking the device +up by ifindex. + +Reported-by: sashiko-bot@kernel.org +Closes: https://sashiko.dev/#/patchset/20260707094716.63578-1-socketcan@hartkopp.net +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 44 ++++++++++++++++++++++++-------------------- + 1 file changed, 24 insertions(+), 20 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 0c8e1b58770582..5f289388d261e3 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -889,6 +889,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op) + + /* mark as removed subscription */ + op->rx_reg_dev = NULL; ++ dev_put(dev); + } else + printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device " + "mismatch %p %p\n", op->rx_reg_dev, dev); +@@ -919,17 +920,14 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh, + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the dev. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(sock_net(op->sk), +- op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(sock_net(op->sk), NULL, + op->can_id, +@@ -1452,7 +1450,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + bcm_rx_handler, op, + "bcm", sk); + +- op->rx_reg_dev = dev; ++ /* keep a reference so that a later ++ * unregister can safely reach the device even ++ * if a concurrent NETDEV_UNREGISTER has ++ * already unlisted it by ifindex ++ */ ++ if (!err) { ++ op->rx_reg_dev = dev; ++ dev_hold(dev); ++ } + dev_put(dev); + } else { + /* the requested device is gone - do not +@@ -1825,16 +1831,14 @@ static int bcm_release(struct socket *sock) + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the device. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(net, op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(net, NULL, op->can_id, + REGMASK(op->can_id), +-- +2.53.0 + diff --git a/queue-6.1/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch b/queue-6.1/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch new file mode 100644 index 0000000000..b8cc706581 --- /dev/null +++ b/queue-6.1/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch @@ -0,0 +1,230 @@ +From 12c6352112d4e36865e4a1403d20271aaa57b90f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:24 +0200 +Subject: can: bcm: extend bcm_tx_lock usage for data and timer updates + +From: Oliver Hartkopp + +commit 12ce799f7ab1e05bd8fbf79e46f403bfe5597ebc upstream. + +Stage new CAN frame content for an existing tx op into a kmalloc()'d +buffer and validate it there, mirroring the approach already used in +bcm_rx_setup(). Only copy the validated data into op->frames while +holding op->bcm_tx_lock, so bcm_can_tx() and bcm_tx_timeout_handler() +can no longer observe a partially updated or unvalidated frame. + +Add a missing error path for memcpy_from_msg() when copying CAN frame +data from userspace. + +Also move the kt_ival1/kt_ival2/ival1/ival2 updates in bcm_tx_setup() +under op->bcm_tx_lock, and read kt_ival1/kt_ival2/count under the same +lock in bcm_tx_set_expiry() and bcm_tx_timeout_handler(), closing the +torn 64-bit ktime_t read on 32-bit platforms. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 104 ++++++++++++++++++++++++++++++++++++-------------- + 1 file changed, 75 insertions(+), 29 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index be74888c64c6f0..953b62965466f3 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -125,7 +125,7 @@ struct bcm_op { + struct canfd_frame last_sframe; + struct sock *sk; + struct net_device *rx_reg_dev; +- spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_tx_lock; /* protect tx data and timer updates */ + spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + +@@ -440,12 +440,18 @@ static bool bcm_tx_set_expiry(struct bcm_op *op, struct hrtimer *hrt) + { + ktime_t ival; + ++ spin_lock_bh(&op->bcm_tx_lock); ++ + if (op->kt_ival1 && op->count) + ival = op->kt_ival1; +- else if (op->kt_ival2) ++ else if (op->kt_ival2) { + ival = op->kt_ival2; +- else ++ } else { ++ spin_unlock_bh(&op->bcm_tx_lock); + return false; ++ } ++ ++ spin_unlock_bh(&op->bcm_tx_lock); + + hrtimer_set_expires(hrt, ktime_add(ktime_get(), ival)); + return true; +@@ -462,25 +468,47 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; ++ bool tx_ival1, tx_ival2; ++ ++ /* snapshot kt_ival1/kt_ival2/count under lock to avoid torn ++ * ktime_t reads racing with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ tx_ival1 = op->kt_ival1 && (op->count > 0); ++ tx_ival2 = !!op->kt_ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (tx_ival1) { ++ u32 flags, count; ++ struct bcm_timeval ival1, ival2; + +- if (op->kt_ival1 && (op->count > 0)) { + bcm_can_tx(op, NULL); +- if (!op->count && (op->flags & TX_COUNTEVT)) { + ++ /* snapshot variables under lock to avoid torn reads racing ++ * with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ flags = op->flags; ++ count = op->count; ++ ival1 = op->ival1; ++ ival2 = op->ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (!count && (flags & TX_COUNTEVT)) { + /* create notification to user */ + memset(&msg_head, 0, sizeof(msg_head)); + msg_head.opcode = TX_EXPIRED; +- msg_head.flags = op->flags; +- msg_head.count = op->count; +- msg_head.ival1 = op->ival1; +- msg_head.ival2 = op->ival2; ++ msg_head.flags = flags; ++ msg_head.count = count; ++ msg_head.ival1 = ival1; ++ msg_head.ival2 = ival2; + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + + bcm_send_to_user(op, &msg_head, NULL, 0); + } + +- } else if (op->kt_ival2) { ++ } else if (tx_ival2) { + bcm_can_tx(op, NULL); + } + +@@ -988,6 +1016,8 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->tx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames; ++ + /* update existing BCM operation */ + + /* +@@ -998,11 +1028,23 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes > op->nframes) + return -E2BIG; + +- /* update CAN frames content */ ++ /* get new CAN frames content into a staging buffer before ++ * locking: validate and normalize the frames there so that ++ * bcm_can_tx() / bcm_tx_timeout_handler() never observe a ++ * partially updated or unvalidated frame in op->frames ++ */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ + for (i = 0; i < msg_head->nframes; i++) { + +- cf = op->frames + op->cfsiz * i; ++ cf = new_frames + op->cfsiz * i; + err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + + if (op->flags & CAN_FD_FRAME) { + if (cf->len > 64) +@@ -1012,36 +1054,38 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -EINVAL; + } + +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + + if (msg_head->flags & TX_CP_CAN_ID) { + /* copy can_id into frame */ + cf->can_id = msg_head->can_id; + } + } ++ ++ spin_lock_bh(&op->bcm_tx_lock); ++ ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, msg_head->nframes * op->cfsiz); ++ + op->flags = msg_head->flags; + +- /* only lock for unlikely count/nframes/currframe changes */ + if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX || +- op->flags & SETTIMER) { +- +- spin_lock_bh(&op->bcm_tx_lock); ++ op->flags & TX_RESET_MULTI_IDX) { ++ /* potentially update changed nframes */ ++ op->nframes = msg_head->nframes; ++ /* restart multiple frame transmission */ ++ op->currframe = 0; ++ } + +- if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX) { +- /* potentially update changed nframes */ +- op->nframes = msg_head->nframes; +- /* restart multiple frame transmission */ +- op->currframe = 0; +- } ++ if (op->flags & SETTIMER) ++ op->count = msg_head->count; + +- if (op->flags & SETTIMER) +- op->count = msg_head->count; ++ spin_unlock_bh(&op->bcm_tx_lock); + +- spin_unlock_bh(&op->bcm_tx_lock); +- } ++ kfree(new_frames); + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1118,10 +1162,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + if (op->flags & SETTIMER) { + /* set timer values */ ++ spin_lock_bh(&op->bcm_tx_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ spin_unlock_bh(&op->bcm_tx_lock); + + /* disable an active timer due to zero values? */ + if (!op->kt_ival1 && !op->kt_ival2) +-- +2.53.0 + diff --git a/queue-6.1/can-bcm-fix-can-frame-rx-tx-statistics.patch b/queue-6.1/can-bcm-fix-can-frame-rx-tx-statistics.patch new file mode 100644 index 0000000000..ac48cd15cc --- /dev/null +++ b/queue-6.1/can-bcm-fix-can-frame-rx-tx-statistics.patch @@ -0,0 +1,191 @@ +From 5f5ca067c97e7c7a7c88deba16f8da61e8a1819c Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:23 +0200 +Subject: can: bcm: fix CAN frame rx/tx statistics + +From: Oliver Hartkopp + +commit e6c24ba95fc3f1b5e1dcd28b1c6e59ef61a9daa5 upstream. + +KCSAN detected a data race within the bcm_rx_handler() when two CAN frames +have been simultaneously received and processed in a single rx op by two +different CPUs. + +Use atomic operations with (signed) long data types to access the +statistics in the hot path to fix the KCSAN complaint. + +Additionally simplify the update and check of statistics overflow by +using the atomic operations in separate bcm_update_[rx|tx]_stats() +functions. The rx variant runs under bcm_rx_update_lock to prevent +races when resetting the two rx counters; the tx variant runs under +bcm_tx_lock and only needs to guard its own counter's overflow. + +As the rx path resets its values already at LONG_MAX / 100, there is +no conflict between the two locking domains (bcm_rx_update_lock vs. +bcm_tx_lock) even for ops that use both paths. + +The rx statistics update and the frames_filtered update in +bcm_rx_changed() were previously performed in two separate +bcm_rx_update_lock sections. For an rx op subscribed on all interfaces +(ifindex == 0), bcm_rx_handler() can run concurrently on different +CPUs, so a counter reset by one CPU between these two sections could +leave frames_filtered larger than frames_abs on another CPU, producing +a bogus (even negative) reduction percentage in procfs. Update the +statistics in the same critical section as bcm_rx_changed() to close +this gap, which also removes the now unneeded extra lock/unlock pair +around the traffic_flags calculation. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 67 ++++++++++++++++++++++++++++++++++----------------- + 1 file changed, 45 insertions(+), 22 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 5673cda3df2c62..be74888c64c6f0 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -109,7 +109,7 @@ struct bcm_op { + int ifindex; + canid_t can_id; + u32 flags; +- unsigned long frames_abs, frames_filtered; ++ atomic_long_t frames_abs, frames_filtered; + struct bcm_timeval ival1, ival2; + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; +@@ -216,10 +216,13 @@ static int bcm_proc_show(struct seq_file *m, void *v) + + list_for_each_entry_rcu(op, &bo->rx_ops, list) { + +- unsigned long reduction; ++ long reduction, frames_filtered, frames_abs; ++ ++ frames_filtered = atomic_long_read(&op->frames_filtered); ++ frames_abs = atomic_long_read(&op->frames_abs); + + /* print only active entries & prevent division by zero */ +- if (!op->frames_abs) ++ if (!frames_abs) + continue; + + seq_printf(m, "rx_op: %03X %-5s ", op->can_id, +@@ -241,9 +244,9 @@ static int bcm_proc_show(struct seq_file *m, void *v) + (long long)ktime_to_us(op->kt_ival2)); + + seq_printf(m, "# recv %ld (%ld) => reduction: ", +- op->frames_filtered, op->frames_abs); ++ frames_filtered, frames_abs); + +- reduction = 100 - (op->frames_filtered * 100) / op->frames_abs; ++ reduction = 100 - (frames_filtered * 100) / frames_abs; + + seq_printf(m, "%s%ld%%\n", + (reduction == 100) ? "near " : "", reduction); +@@ -267,7 +270,8 @@ static int bcm_proc_show(struct seq_file *m, void *v) + seq_printf(m, "t2=%lld ", + (long long)ktime_to_us(op->kt_ival2)); + +- seq_printf(m, "# sent %ld\n", op->frames_abs); ++ seq_printf(m, "# sent %ld\n", ++ atomic_long_read(&op->frames_abs)); + } + seq_putc(m, '\n'); + +@@ -277,6 +281,24 @@ static int bcm_proc_show(struct seq_file *m, void *v) + } + #endif /* CONFIG_PROC_FS */ + ++static void bcm_update_rx_stats(struct bcm_op *op) ++{ ++ /* prevent overflow of the reduction% calculation in bcm_proc_show() */ ++ if (atomic_long_inc_return(&op->frames_abs) > LONG_MAX / 100) { ++ atomic_long_set(&op->frames_filtered, 0); ++ atomic_long_set(&op->frames_abs, 0); ++ } ++} ++ ++static void bcm_update_tx_stats(struct bcm_op *op) ++{ ++ /* tx_op has no reduction% calculation - use the full range and ++ * just keep the displayed counter non-negative on overflow ++ */ ++ if (atomic_long_inc_return(&op->frames_abs) == LONG_MAX) ++ atomic_long_set(&op->frames_abs, 0); ++} ++ + /* + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op +@@ -328,7 +350,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + spin_lock_bh(&op->bcm_tx_lock); + + if (!err) +- op->frames_abs++; ++ bcm_update_tx_stats(op); + + /* only advance the cyclic sequence if nothing reset currframe while + * we were sending - a concurrent TX_RESET_MULTI_IDX means this +@@ -473,12 +495,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data) + { + struct bcm_msg_head head; + +- /* update statistics */ +- op->frames_filtered++; +- +- /* prevent statistics overflow */ +- if (op->frames_filtered > ULONG_MAX/100) +- op->frames_filtered = op->frames_abs = 0; ++ /* update statistics (frames_filtered <= frames_abs) */ ++ if (atomic_long_read(&op->frames_abs)) ++ atomic_long_inc(&op->frames_filtered); + + /* this element is not throttled anymore */ + data->flags &= (BCM_CAN_FLAGS_MASK|RX_RECV); +@@ -718,25 +737,29 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + op->rx_stamp = skb->tstamp; + /* save originator for recvfrom() */ + op->rx_ifindex = skb->dev->ifindex; +- /* update statistics */ +- op->frames_abs++; + +- /* snapshot the flag under lock: op->flags/op->frames may be updated +- * concurrently by bcm_rx_setup(). +- */ ++ /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); ++ + rtr_frame = op->flags & RX_RTR_FRAME; +- if (rtr_frame) ++ if (rtr_frame) { ++ bcm_update_rx_stats(op); ++ /* snapshot RTR content under lock */ + memcpy(&rtrframe, op->frames, op->cfsiz); +- spin_unlock_bh(&op->bcm_rx_update_lock); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + +- if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ + bcm_can_tx(op, &rtrframe); + return; + } + +- spin_lock_bh(&op->bcm_rx_update_lock); ++ /* update statistics in the same critical section as bcm_rx_changed() ++ * below: frames_filtered must never be checked/incremented against a ++ * frames_abs snapshot from a concurrent bcm_rx_handler() call on ++ * another CPU for the same (wildcard) op, or frames_filtered can end ++ * up larger than frames_abs. ++ */ ++ bcm_update_rx_stats(op); + + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ +-- +2.53.0 + diff --git a/queue-6.1/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch b/queue-6.1/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch new file mode 100644 index 0000000000..7f805b6fba --- /dev/null +++ b/queue-6.1/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch @@ -0,0 +1,74 @@ +From 3338e0cdfd17729b6f34b80db863e5cd6a744e9d Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:28 +0200 +Subject: can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler() + +From: Oliver Hartkopp + +commit 58fd6cbc8541216af1d7ed272ea7ac2b66d50fd8 upstream. + +For an rx op subscribed on all interfaces (ifindex == 0), the same op +is registered once in the shared per-netns wildcard filter list, so +bcm_rx_handler() can run concurrently on different CPUs for frames +arriving on different net devices. + +op->rx_stamp and op->rx_ifindex were written before bcm_rx_update_lock was +taken, allowing concurrent writers to race each other - including a torn +store of the 64-bit rx_stamp on 32-bit platforms. + +Beyond a torn store bcm_send_to_user() must report the timestamp/ifindex +of the very same frame whose content it is delivering. So the assignment +is placed in the same unbroken bcm_rx_update_lock section as the content +comparison. + +As a side effect, the RTR-request frame feature (which never reach +bcm_send_to_user()) no longer updates rx_stamp/rx_ifindex, since only +the notification path needs them. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260707145135.5BC831F00A3A@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index c304279d667b4f..e6eaf7dfcb2ac5 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -761,11 +761,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* disable timeout */ + hrtimer_cancel(&op->timer); + +- /* save rx timestamp */ +- op->rx_stamp = skb->tstamp; +- /* save originator for recvfrom() */ +- op->rx_ifindex = skb->dev->ifindex; +- + /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); + +@@ -789,6 +784,14 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + */ + bcm_update_rx_stats(op); + ++ /* save rx timestamp and originator for recvfrom() under lock. ++ * For an op subscribed on all interfaces (ifindex == 0) ++ * bcm_rx_handler() can run concurrently on different CPUs so ++ * the CAN content and the meta data must be bundled correctly. ++ */ ++ op->rx_stamp = skb->tstamp; ++ op->rx_ifindex = skb->dev->ifindex; ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe); +-- +2.53.0 + diff --git a/queue-6.1/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch b/queue-6.1/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch new file mode 100644 index 0000000000..1f998d140e --- /dev/null +++ b/queue-6.1/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch @@ -0,0 +1,164 @@ +From d907329fb8da4ee2b9b73e558642700429adbd01 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:27 +0200 +Subject: can: bcm: fix stale rx/tx ops after device removal + +From: Oliver Hartkopp + +commit 3b762c0d950383ab7a002686c9136b9aa55d2d70 upstream. + +RX: an RX_SETUP update(!) for an existing op skipped can_rx_register() +unconditionally, even when a concurrent NETDEV_UNREGISTER had already +torn down its registration (op->rx_reg_dev == NULL). This silently +did not re-enable frame delivery for that updated filter. bcm_rx_setup() +now re-registers in that case, while leaving rx_ops with ifindex = 0 +(all CAN devices) which never carry a tracked rx_reg_dev registered as-is. + +TX: bcm_notify() only handled bo->rx_ops on NETDEV_UNREGISTER, leaving +tx_ops with an active cyclic transmission re-arming its hrtimer +indefinitely to execute bcm_tx_timeout_handler(). Cancelling the hrtimer +prevents the runaway timer and any injection into a later reused ifindex, +since nothing else calls bcm_can_tx() for the op until an explicit +TX_SETUP update re-arms it. + +Unlike bcm_rx_unreg(), which clears the tracked rx_reg_dev for rx_ops, +the ifindex is intentionally left unchanged for tx_ops. bcm_tx_setup() +always rejects ifindex 0, so clearing it would strand the op: neither a +later TX_SETUP (bcm_find_op()) nor TX_DELETE (bcm_delete_tx_op()) could +ever find it again, since both require an exact ifindex match. + +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260708094536.DDF821F00A3A@smtp.kernel.org/ +Closes: https://lore.kernel.org/linux-can/20260708154039.347ED1F000E9@smtp.kernel.org/ +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 54 +++++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 44 insertions(+), 10 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 5f289388d261e3..c304279d667b4f 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1239,6 +1239,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + struct bcm_sock *bo = bcm_sk(sk); + struct bcm_op *op; + int do_rx_register; ++ int new_op = 0; + int err = 0; + + if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) { +@@ -1323,8 +1324,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* free temporary frames / kfree(NULL) is safe */ + kfree(new_frames); + +- /* Only an update -> do not call can_rx_register() */ +- do_rx_register = 0; ++ /* Don't register a new CAN filter for the rx_op update unless ++ * a concurrent NETDEV_UNREGISTER notifier already tore down ++ * the previous registration. In this case the receiver needs ++ * to be re-registered here so that this update doesn't ++ * silently stop delivering frames for the given ifindex. ++ * Ops with ifindex = 0 (all CAN interfaces) never carry a ++ * tracked rx_reg_dev and stay registered as-is. ++ */ ++ do_rx_register = (ifindex && !op->rx_reg_dev) ? 1 : 0; + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1394,6 +1402,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + /* call can_rx_register() */ + do_rx_register = 1; ++ new_op = 1; + + } /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */ + +@@ -1407,7 +1416,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (op->flags & SETTIMER) { + + /* set timers (locked) for newly created op */ +- if (do_rx_register) { ++ if (new_op) { + spin_lock_bh(&op->bcm_rx_update_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; +@@ -1437,7 +1446,10 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + HRTIMER_MODE_REL_SOFT); + } + +- /* now we can register for can_ids, if we added a new bcm_op */ ++ /* now we can register for can_ids, if we added a new bcm_op ++ * or need to re-register after a NETDEV_UNREGISTER tore down ++ * the previous registration of an existing op ++ */ + if (do_rx_register) { + if (ifindex) { + struct net_device *dev; +@@ -1467,18 +1479,32 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -ENODEV; + } + +- } else ++ } else { + err = can_rx_register(sock_net(sk), NULL, op->can_id, + REGMASK(op->can_id), + bcm_rx_handler, op, "bcm", sk); ++ } ++ + if (err) { +- /* this bcm rx op is broken -> remove it */ +- bcm_remove_op(op); ++ /* newly created bcm rx op is broken -> remove it */ ++ if (new_op) { ++ bcm_remove_op(op); ++ return err; ++ } ++ ++ /* an existing op just stays unregistered. ++ * Cancel op->timer and (defensively) op->thrtimer. ++ * Other settings can't be reached until the next ++ * successful RX_SETUP. ++ */ ++ hrtimer_cancel(&op->timer); ++ hrtimer_cancel(&op->thrtimer); + return err; + } + +- /* add this bcm_op to the list of the rx_ops */ +- list_add_rcu(&op->list, &bo->rx_ops); ++ /* add a new bcm_op to the list of the rx_ops */ ++ if (new_op) ++ list_add_rcu(&op->list, &bo->rx_ops); + } + + return msg_head->nframes * op->cfsiz + MHSIZ; +@@ -1694,11 +1720,19 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + case NETDEV_UNREGISTER: + lock_sock(sk); + +- /* remove device specific receive entries */ ++ /* rx_ops: remove device specific receive entries */ + list_for_each_entry(op, &bo->rx_ops, list) + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* tx_ops: stop device specific cyclic transmissions on the ++ * vanishing ifindex. Cancelling the timer is enough to stop ++ * cyclic bcm_can_tx() calls as there is no re-arming. ++ */ ++ list_for_each_entry(op, &bo->tx_ops, list) ++ if (op->ifindex == dev->ifindex) ++ hrtimer_cancel(&op->timer); ++ + /* remove device reference, if this is our bound device */ + if (bo->bound && bo->ifindex == dev->ifindex) { + #if IS_ENABLED(CONFIG_PROC_FS) +-- +2.53.0 + diff --git a/queue-6.1/can-bcm-track-a-single-source-interface-for-anydev-t.patch b/queue-6.1/can-bcm-track-a-single-source-interface-for-anydev-t.patch new file mode 100644 index 0000000000..3c4bb6d26f --- /dev/null +++ b/queue-6.1/can-bcm-track-a-single-source-interface-for-anydev-t.patch @@ -0,0 +1,143 @@ +From 1a4fc84f1ff2ec9bc3873c9554f56bcbc379d187 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:29 +0200 +Subject: can: bcm: track a single source interface for ANYDEV timeout/throttle + ops + +From: Oliver Hartkopp + +commit 2f5976f54a04e9f18b25283036ac3136be453b17 upstream. + +An ANYDEV rx op (ifindex == 0) with an active RX timeout and/or +throttle timer has no defined semantics when matching frames arrive +from several interfaces: bcm_rx_handler() can run concurrently for +the same op on different CPUs, racing hrtimer_cancel()/ +bcm_rx_starttimer() against bcm_rx_timeout_handler() and causing +spurious RX_TIMEOUT notifications and last_frames corruption. The +same concurrency lets throttled multiplex frames from different +interfaces clobber the single rx_ifindex/rx_stamp fields shared by +the op. + +Add op->if_detected to track the first interface that delivers a +matching frame while a timeout/throttle timer is configured, and +reject frames from any other interface for that op. The claim is +decided in bcm_rx_handler() before hrtimer_cancel() touches +op->timer, so a rejected frame can never disturb the claimed +interface's watchdog. RTR-mode ops are excluded via RX_RTR_FRAME, +independent of kt_ival1/kt_ival2, since those may briefly hold a +stale value from an earlier non-RTR configuration. + +The claim is released in bcm_notify() on NETDEV_UNREGISTER and in +bcm_rx_setup() when SETTIMER reconfigures the timer values. + +A (re-)claim is only possible on CAN devices in NETREG_REGISTERED +dev->reg_state to cover the release in bcm_notify() where reg_state +becomes NETREG_UNREGISTERING until synchronize_net(). + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260709105031.1A39C1F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 44 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index e6eaf7dfcb2ac5..16640650c43384 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -114,6 +114,7 @@ struct bcm_op { + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; + int rx_ifindex; ++ int if_detected; /* first received ifindex in ANYDEV rx_op mode */ + int cfsiz; + u32 count; + u32 nframes; +@@ -758,6 +759,33 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + return; + } + ++ /* An ANYDEV op with an active RX timeout and/or throttle timer ++ * tracks a single source interface: claim the first interface that ++ * delivers a matching frame and reject frames from any other one, ++ * before hrtimer_cancel() below can touch op->timer - this avoids ++ * racing bcm_rx_timeout_handler() across concurrent interfaces. ++ * RX_RTR_FRAME ops are excluded, as kt_ival1/kt_ival2 may briefly ++ * hold a stale value from an earlier non-RTR configuration. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ if (!(op->flags & RX_RTR_FRAME) && ++ (op->kt_ival1 || op->kt_ival2)) { ++ /* don't claim to vanishing interface */ ++ if (!op->if_detected && ++ skb->dev->reg_state == NETREG_REGISTERED) ++ op->if_detected = skb->dev->ifindex; ++ ++ if (op->if_detected != skb->dev->ifindex) { ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ return; ++ } ++ } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ + /* disable timeout */ + hrtimer_cancel(&op->timer); + +@@ -784,10 +812,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + */ + bcm_update_rx_stats(op); + +- /* save rx timestamp and originator for recvfrom() under lock. +- * For an op subscribed on all interfaces (ifindex == 0) +- * bcm_rx_handler() can run concurrently on different CPUs so +- * the CAN content and the meta data must be bundled correctly. ++ /* save rx timestamp and originator for recvfrom() under lock: an ++ * ANYDEV op without an active timer can still run concurrently on ++ * different CPUs, so content and meta data must be bundled here. + */ + op->rx_stamp = skb->tstamp; + op->rx_ifindex = skb->dev->ifindex; +@@ -1321,6 +1348,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + op->kt_lastmsg = 0; ++ op->if_detected = 0; /* reclaim ifindex in ANYDEV mode */ + } + spin_unlock_bh(&op->bcm_rx_update_lock); + +@@ -1724,10 +1752,21 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + lock_sock(sk); + + /* rx_ops: remove device specific receive entries */ +- list_for_each_entry(op, &bo->rx_ops, list) ++ list_for_each_entry(op, &bo->rx_ops, list) { + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* release an ANYDEV op's claim (see bcm_rx_handler()) ++ * on this now confirmed-gone interface. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ if (op->if_detected == dev->ifindex) ++ op->if_detected = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ } ++ + /* tx_ops: stop device specific cyclic transmissions on the + * vanishing ifindex. Cancelling the timer is enough to stop + * cyclic bcm_can_tx() calls as there is no re-arming. +-- +2.53.0 + diff --git a/queue-6.1/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch b/queue-6.1/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch new file mode 100644 index 0000000000..2c1bd900a5 --- /dev/null +++ b/queue-6.1/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch @@ -0,0 +1,128 @@ +From 3c2f418d15b25958881d0bbdbfb138f1060553ae Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:23:25 +0200 +Subject: can: bcm: validate frame length in bcm_rx_setup() for RTR replies + +From: Oliver Hartkopp + +commit 62ec41f364648be79d54d94d0d240ee326948afd upstream. + +bcm_tx_setup() validates cf->len against the CAN/CAN FD DLC limits +before installing frames for TX_SETUP, but bcm_rx_setup() never did +the same for the RTR-reply frame configured via RX_SETUP with +RX_RTR_FRAME. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++---------------- + 1 file changed, 41 insertions(+), 18 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 953b62965466f3..0c8e1b58770582 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1199,22 +1199,37 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +-static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, +- struct bcm_op *op, void *new_frames) ++static int bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) + { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if (!(msg_head->flags & RX_RTR_FRAME)) ++ return 0; ++ ++ /* this frame is sent out as-is by bcm_can_tx() whenever a matching ++ * remote request is received, so validate its length the same way ++ * bcm_tx_setup() validates TX_SETUP frames before installing it ++ */ ++ if (msg_head->flags & CAN_FD_FRAME) { ++ if (frame0->len > 64) ++ return -EINVAL; ++ } else { ++ if (frame0->len > 8) ++ return -EINVAL; ++ } ++ + /* funny feature in RX(!)_SETUP only for RTR-mode: + * copy can_id into frame BUT without RTR-flag to + * prevent a full-load-loopback-test ... ;-] + * normalize this on the staged buffer, before it is + * ever installed into op->frames. + */ +- if (msg_head->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = new_frames; ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; + +- if ((msg_head->flags & TX_CP_CAN_ID) || +- frame0->can_id == op->can_id) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- } ++ return 0; + } + + /* +@@ -1277,7 +1292,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +- bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + } + + spin_lock_bh(&op->bcm_rx_update_lock); +@@ -1350,16 +1369,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes) { + err = memcpy_from_msg(op->frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) { +- if (op->frames != &op->sframe) +- kfree(op->frames); +- if (op->last_frames != &op->last_sframe) +- kfree(op->last_frames); +- kfree(op); +- return err; +- } ++ if (err < 0) ++ goto free_op; + +- bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ if (err < 0) ++ goto free_op; + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1461,6 +1476,14 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + } + + return msg_head->nframes * op->cfsiz + MHSIZ; ++ ++free_op: ++ if (op->frames != &op->sframe) ++ kfree(op->frames); ++ if (op->last_frames != &op->last_sframe) ++ kfree(op->last_frames); ++ kfree(op); ++ return err; + } + + /* +-- +2.53.0 + diff --git a/queue-6.1/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch b/queue-6.1/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch new file mode 100644 index 0000000000..f056f614bb --- /dev/null +++ b/queue-6.1/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch @@ -0,0 +1,238 @@ +From 6b79050cbe96ba501db9df10a87cdb8f8444d134 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:33:36 +0200 +Subject: can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER + +From: Oliver Hartkopp + +commit 20bab8b88baac140ca3701116e1d486c7f51e311 upstream. + +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|rx].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 +Closes: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-2-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 88 +++++++++++++++++++++++++++++++++---------------- + 1 file changed, 59 insertions(+), 29 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index 7d9327b3211078..23affae810dd0a 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -137,6 +137,7 @@ struct isotp_sock { + struct sock sk; + int bound; + int ifindex; ++ struct net_device *dev; + canid_t txid; + canid_t rxid; + ktime_t tx_gap; +@@ -939,6 +940,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + 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; ++ } ++ + if (!size || size > MAX_MSG_LENGTH) { + err = -EINVAL; + goto err_out_drop; +@@ -1164,28 +1173,30 @@ static int isotp_release(struct socket *sock) + 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); ++ dev_put(so->dev); + } + ++ 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 +@@ -1198,9 +1209,6 @@ static int isotp_release(struct socket *sock) + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->rxtimer); + +- so->ifindex = 0; +- so->bound = 0; +- + sock_orphan(sk); + sock->sk = NULL; + +@@ -1254,6 +1262,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + if (!addr->can_ifindex) + return -ENODEV; + ++ rtnl_lock(); + lock_sock(sk); + + if (so->bound) { +@@ -1261,6 +1270,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + ++ /* A transmission or reception that outlived a previous binding ++ * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo ++ * and RX 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 || so->rx.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; +@@ -1273,14 +1293,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + if (dev->type != ARPHRD_CAN) { +- dev_put(dev); + err = -ENODEV; +- goto out; ++ goto out_put_dev; + } +- if (dev->mtu < so->ll.mtu) { +- dev_put(dev); ++ if (READ_ONCE(dev->mtu) < so->ll.mtu) { + err = -EINVAL; +- goto out; ++ goto out_put_dev; + } + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; +@@ -1298,16 +1316,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + 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; ++ dev_hold(so->dev); ++ ++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; +@@ -1510,7 +1537,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + if (!net_eq(dev_net(dev), sock_net(sk))) + return; + +- if (so->ifindex != dev->ifindex) ++ if (so->dev != dev) + return; + + switch (msg) { +@@ -1526,10 +1553,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + can_rx_unregister(dev_net(dev), dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); ++ dev_put(so->dev); + } + + so->ifindex = 0; + so->bound = 0; ++ so->dev = NULL; + release_sock(sk); + + sk->sk_err = ENODEV; +@@ -1574,6 +1603,7 @@ static int isotp_init(struct sock *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; +-- +2.53.0 + diff --git a/queue-6.1/can-isotp-serialize-tx-state-transitions-under-so-rx.patch b/queue-6.1/can-isotp-serialize-tx-state-transitions-under-so-rx.patch new file mode 100644 index 0000000000..0fb725a0a9 --- /dev/null +++ b/queue-6.1/can-isotp-serialize-tx-state-transitions-under-so-rx.patch @@ -0,0 +1,426 @@ +From a9f5e019b0df179b5580885a59eaeb9cd01eccc9 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:33:37 +0200 +Subject: can: isotp: serialize TX state transitions under so->rx_lock + +From: Oliver Hartkopp + +commit cf070fe33bfbd1a4c21236078fadb35dd223a157 upstream. + +The TX state machine (so->tx.state) is driven from three contexts: +sendmsg() claiming and progressing a transfer, the RX path consuming +Flow Control/echo frames, and two hrtimers timing out a stalled +transfer. Mixing a lock-free cmpxchg() claim in sendmsg() with +hrtimer_cancel() calls made under so->rx_lock elsewhere left windows +where a frame or timer callback could act on a state that had already +moved on, corrupting an unrelated transfer. + +so->rx_lock now covers the full lifecycle of a TX claim: sendmsg() +takes it to check so->tx.state is ISOTP_IDLE, switch it to +ISOTP_SENDING, bump so->tx_gen and drain the previous transfer's +timers - all as one critical section. isotp_rcv_fc()/isotp_rcv_cf() +already run under this lock via isotp_rcv(), and isotp_rcv_echo() now +takes it itself, so none of them can ever observe a transfer mid-claim. +This also means a transfer can no longer be handed to sendmsg()'s +cleanup paths (signal or send error) while another thread is +concurrently claiming or finishing it, so those paths can cancel +timers and reset the state unconditionally. + +isotp_release() claims the socket the same way, so a racing sendmsg() +sees a consistent ISOTP_SHUTDOWN and skips arming its timer or sending. + +Only the hrtimer callbacks stay outside so->rx_lock, since they run +under so->rx_lock's cancellation elsewhere and taking it themselves +would deadlock. so->tx_gen lets them recognize whether the transfer +they timed out is still the one currently active, so they don't +report an error against a transfer that has since completed or been +superseded. + +Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-3-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 192 ++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 154 insertions(+), 38 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index 23affae810dd0a..6ee4849e4b789a 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -142,7 +142,7 @@ struct isotp_sock { + canid_t rxid; + ktime_t tx_gap; + ktime_t lastrxcf_tstamp; +- struct hrtimer rxtimer, txtimer, txfrtimer; ++ struct hrtimer rxtimer, txtimer, txfrtimer, echotimer; + struct can_isotp_options opt; + struct can_isotp_fc_options rxfc, txfc; + struct can_isotp_ll_options ll; +@@ -150,6 +150,7 @@ struct isotp_sock { + u32 force_tx_stmin; + u32 force_rx_stmin; + u32 cfecho; /* consecutive frame echo tag */ ++ u32 tx_gen; /* generation, bumped per new tx transfer */ + struct tpcon rx, tx; + struct list_head notifier; + wait_queue_head_t wait; +@@ -356,6 +357,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + + hrtimer_cancel(&so->txtimer); + ++ /* isotp_tx_timeout() may have given up on this job while ++ * hrtimer_cancel() above waited for it to finish; so->rx_lock ++ * (held by our caller isotp_rcv()) rules out a concurrent claim, ++ * so a plain recheck is enough here. ++ */ ++ if (so->tx.state != ISOTP_WAIT_FC && ++ so->tx.state != ISOTP_WAIT_FIRST_FC) ++ return 1; ++ + if ((cf->len < ae + FC_CONTENT_SZ) || + ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { +@@ -401,7 +411,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + so->tx.bs = 0; + so->tx.state = ISOTP_SENDING; + /* send CF frame and enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); + break; +@@ -544,6 +554,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; ++ + /* CFs are never longer than the FF */ + if (cf->len > so->rx.ll_dl) + return 1; +@@ -833,20 +851,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + + /* only handle my own local echo CF/SF skb's (no FF!) */ +- if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) ++ if (skb->sk != sk) + return; + ++ /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock ++ * (no isotp_rcv() caller here), so take it ourselves ++ */ ++ spin_lock(&so->rx_lock); ++ ++ /* so->cfecho may since belong to a new transfer; recheck under lock */ ++ if (so->cfecho != *(u32 *)cf->data) ++ goto out_unlock; ++ + /* cancel local echo timeout */ +- hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + + /* local echo skb with consecutive frame has been consumed */ + so->cfecho = 0; + ++ /* claiming a transfer also takes so->rx_lock, so a plain recheck ++ * is enough: so->tx.state can't have flipped to ISOTP_SENDING for ++ * a new claim while we're still in here ++ */ ++ if (so->tx.state != ISOTP_SENDING) ++ goto out_unlock; ++ + if (so->tx.idx >= so->tx.len) { + /* we are done */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); +- return; ++ goto out_unlock; + } + + if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { +@@ -854,53 +888,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + so->tx.state = ISOTP_WAIT_FC; + hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); +- return; ++ goto out_unlock; + } + + /* no gap between data frames needed => use burst mode */ + if (!so->tx_gap) { + /* enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); +- return; ++ goto out_unlock; + } + + /* start timer to send next consecutive frame with correct delay */ + hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT); ++ ++out_unlock: ++ spin_unlock(&so->rx_lock); + } + +-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get ++ * cancelled under so->rx_lock elsewhere, so this must stay lock-free to ++ * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting ++ * a new transfer with an error from the one that just timed out. ++ */ ++static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) + { +- struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, +- txtimer); + struct sock *sk = &so->sk; ++ u32 gen = READ_ONCE(so->tx_gen); ++ u32 old_state = READ_ONCE(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 the timeout if the state is still unchanged */ ++ 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); ++ if (READ_ONCE(so->tx_gen) == gen) { ++ /* 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; + } + ++/* so->txtimer: fires when a Flow Control frame does not arrive in time */ ++static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ txtimer); ++ ++ return isotp_tx_timeout(so); ++} ++ ++/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */ ++static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ echotimer); ++ ++ return isotp_tx_timeout(so); ++} ++ + static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) + { + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txfrtimer); + + /* start echo timeout handling and cover below protocol error */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + + /* cfecho should be consumed by isotp_rcv_echo() here */ +@@ -920,13 +984,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; + s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; ++ struct hrtimer *tx_hrt = &so->echotimer; ++ u32 new_state = ISOTP_SENDING; + int off; + int err; + + if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) + return -EADDRNOTAVAIL; + +- while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) { ++ /* claim the socket under so->rx_lock: this serializes the claim ++ * with the RX path and with sendmsg()'s own error paths below, so ++ * none of them can ever see a transfer mid-claim ++ */ ++ for (;;) { ++ spin_lock_bh(&so->rx_lock); ++ if (READ_ONCE(so->tx.state) == ISOTP_IDLE) ++ break; ++ spin_unlock_bh(&so->rx_lock); ++ + /* we do not support multiple buffers - for now */ + if (msg->msg_flags & MSG_DONTWAIT) + return -EAGAIN; +@@ -935,11 +1010,23 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + return -EADDRNOTAVAIL; + + /* wait for complete transmission of current pdu */ +- err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); ++ err = wait_event_interruptible(so->wait, ++ so->tx.state == ISOTP_IDLE); + if (err) +- goto err_event_drop; ++ return err; + } + ++ /* new transfer: bump so->tx_gen and drain the old one's timers, ++ * still under the so->rx_lock we just claimed the socket with ++ */ ++ WRITE_ONCE(so->tx.state, ISOTP_SENDING); ++ WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); ++ hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); ++ hrtimer_cancel(&so->txfrtimer); ++ so->cfecho = 0; ++ spin_unlock_bh(&so->rx_lock); ++ + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ +@@ -1040,18 +1127,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + so->cfecho = *(u32 *)cf->data; + } else { + /* standard flow control check */ +- so->tx.state = ISOTP_WAIT_FIRST_FC; ++ new_state = ISOTP_WAIT_FIRST_FC; + + /* start timeout for FC */ + hrtimer_sec = ISOTP_FC_TIMEOUT; ++ tx_hrt = &so->txtimer; + + /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ + so->cfecho = 0; + } + } + +- hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0), ++ spin_lock_bh(&so->rx_lock); ++ if (so->tx.state == ISOTP_SHUTDOWN) { ++ /* isotp_release() has since taken over and already drained ++ * our timers - don't send into a socket that's going away ++ */ ++ spin_unlock_bh(&so->rx_lock); ++ kfree_skb(skb); ++ dev_put(dev); ++ wake_up_interruptible(&so->wait); ++ return -EADDRNOTAVAIL; ++ } ++ /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ ++ so->tx.state = new_state; ++ hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), + HRTIMER_MODE_REL_SOFT); ++ spin_unlock_bh(&so->rx_lock); + + /* send the first or only CAN frame */ + cf->flags = so->ll.tx_flags; +@@ -1064,13 +1166,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + pr_notice_once("can-isotp: %s: can_send_ret %pe\n", + __func__, ERR_PTR(err)); + ++ spin_lock_bh(&so->rx_lock); + /* no transmission -> no timeout monitoring */ +- hrtimer_cancel(&so->txtimer); +- +- /* reset consecutive frame echo tag */ +- so->cfecho = 0; +- +- goto err_out_drop; ++ hrtimer_cancel(tx_hrt); ++ goto err_out_drop_locked; + } + + if (wait_tx_done) { +@@ -1086,14 +1185,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + + return size; + ++err_out_drop: ++ /* claimed but nothing sent yet - no timer to cancel */ ++ spin_lock_bh(&so->rx_lock); ++ goto err_out_drop_locked; + err_event_drop: +- /* got signal: force tx state machine to be idle */ +- so->tx.state = ISOTP_IDLE; ++ /* interrupted waiting on our own transfer - drain its timers */ ++ spin_lock_bh(&so->rx_lock); + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); +-err_out_drop: +- /* drop this PDU and unlock a potential wait queue */ ++ hrtimer_cancel(&so->echotimer); ++err_out_drop_locked: ++ /* release the claim; so->rx_lock still held from above */ ++ so->cfecho = 0; + so->tx.state = ISOTP_IDLE; ++ spin_unlock_bh(&so->rx_lock); + wake_up_interruptible(&so->wait); + + return err; +@@ -1155,13 +1261,20 @@ static int isotp_release(struct socket *sock) + so = isotp_sk(sk); + net = sock_net(sk); + +- /* wait for complete transmission of current pdu */ +- while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 && +- cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE) ++ /* best-effort: wait for a running pdu to finish, but don't block on ++ * it forever - give up after the first signal ++ */ ++ while (so->tx.state != ISOTP_IDLE && ++ wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) + ; + +- /* force state machines to be idle also when a signal occurred */ ++ /* claim the socket under so->rx_lock like sendmsg() does, so its ++ * claim can't race the forced ISOTP_SHUTDOWN below; force it ++ * unconditionally, even when a signal cut the wait above short ++ */ ++ spin_lock_bh(&so->rx_lock); + so->tx.state = ISOTP_SHUTDOWN; ++ spin_unlock_bh(&so->rx_lock); + so->rx.state = ISOTP_IDLE; + + spin_lock(&isotp_notifier_lock); +@@ -1207,6 +1320,7 @@ static int isotp_release(struct socket *sock) + + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + hrtimer_cancel(&so->rxtimer); + + sock_orphan(sk); +@@ -1629,6 +1743,8 @@ static int isotp_init(struct sock *sk) + so->rxtimer.function = isotp_rx_timer_handler; + hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txtimer.function = isotp_tx_timer_handler; ++ hrtimer_init(&so->echotimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ so->echotimer.function = isotp_echo_timer_handler; + hrtimer_init(&so->txfrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txfrtimer.function = isotp_txfr_timer_handler; + +-- +2.53.0 + diff --git a/queue-6.1/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch b/queue-6.1/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch new file mode 100644 index 0000000000..2ff00a538e --- /dev/null +++ b/queue-6.1/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch @@ -0,0 +1,201 @@ +From 83c144a22ca97a776f78ac038f1afee0a74e58a3 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 14:58:13 +0200 +Subject: dmaengine: sh: rz-dmac: Move interrupt request after everything is + set up + +From: Claudiu Beznea + +commit 731712403ddb39d1a76a11abf339a0615bc85de7 upstream. + +Once the interrupt is requested, the interrupt handler may run immediately. +Since the IRQ handler can access channel->ch_base, which is initialized +only after requesting the IRQ, this may lead to invalid memory access. +Likewise, the IRQ thread may access uninitialized data (the ld_free, +ld_queue, and ld_active lists), which may also lead to issues. + +Request the interrupts only after everything is set up. To keep the error +path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent(). + +Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC") +Cc: stable@vger.kernel.org +Reviewed-by: Frank Li +Tested-by: John Madieu +Signed-off-by: Claudiu Beznea +Tested-by: Tommaso Merciai +Link: https://patch.msgid.link/20260526084710.3491480-2-claudiu.beznea@kernel.org +[tm: Kept the channel->irq field in rz_dmac_chan_probe() instead of + upstream's local `irq` variable, as commit 04e227718ab8 + ("dmaengine: sh: rz-dmac: Make channel irq local") is not present + in this tree. Likewise kept platform_get_irq_byname() instead of + platform_get_irq_byname_optional() for the error IRQ in rz_dmac_probe(), + as commit 6b3a6b6dc074 ("dmaengine: sh: rz_dmac: make error interrupt + optional") is not present in this tree either; its early return on + failure becomes a goto err jump to match the new call order.] +Signed-off-by: Vinod Koul +Signed-off-by: Tommaso Merciai +Signed-off-by: Sasha Levin +--- + drivers/dma/sh/rz-dmac.c | 96 ++++++++++++++++------------------------ + 1 file changed, 38 insertions(+), 58 deletions(-) + +diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c +index 498e6e24ab0a37..b956292e861940 100644 +--- a/drivers/dma/sh/rz-dmac.c ++++ b/drivers/dma/sh/rz-dmac.c +@@ -779,27 +779,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + channel->index = index; + channel->mid_rid = -EINVAL; + +- /* Request the channel interrupt. */ +- sprintf(pdev_irqname, "ch%u", index); +- channel->irq = platform_get_irq_byname(pdev, pdev_irqname); +- if (channel->irq < 0) +- return channel->irq; +- +- irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", +- dev_name(dmac->dev), index); +- if (!irqname) +- return -ENOMEM; +- +- ret = devm_request_threaded_irq(dmac->dev, channel->irq, +- rz_dmac_irq_handler, +- rz_dmac_irq_handler_thread, 0, +- irqname, channel); +- if (ret) { +- dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", +- channel->irq, ret); +- return ret; +- } +- + /* Set io base address for each channel */ + if (index < 8) { + channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET + +@@ -812,9 +791,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + } + + /* Allocate descriptors */ +- lmdesc = dma_alloc_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- &channel->lmdesc.base_dma, GFP_KERNEL); ++ lmdesc = dmam_alloc_coherent(&pdev->dev, ++ sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, ++ &channel->lmdesc.base_dma, GFP_KERNEL); + if (!lmdesc) { + dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n"); + return -ENOMEM; +@@ -830,7 +809,26 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + INIT_LIST_HEAD(&channel->ld_free); + INIT_LIST_HEAD(&channel->ld_active); + +- return 0; ++ /* Request the channel interrupt. */ ++ sprintf(pdev_irqname, "ch%u", index); ++ channel->irq = platform_get_irq_byname(pdev, pdev_irqname); ++ if (channel->irq < 0) ++ return channel->irq; ++ ++ irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", ++ dev_name(dmac->dev), index); ++ if (!irqname) ++ return -ENOMEM; ++ ++ ret = devm_request_threaded_irq(dmac->dev, channel->irq, ++ rz_dmac_irq_handler, ++ rz_dmac_irq_handler_thread, 0, ++ irqname, channel); ++ if (ret) ++ dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", ++ channel->irq, ret); ++ ++ return ret; + } + + static int rz_dmac_parse_of(struct device *dev, struct rz_dmac *dmac) +@@ -857,7 +855,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + const char *irqname = "error"; + struct dma_device *engine; + struct rz_dmac *dmac; +- int channel_num; + unsigned int i; + int ret; + int irq; +@@ -887,19 +884,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + if (IS_ERR(dmac->ext_base)) + return PTR_ERR(dmac->ext_base); + +- /* Register interrupt handler for error */ +- irq = platform_get_irq_byname(pdev, irqname); +- if (irq < 0) +- return irq; +- +- ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, +- irqname, NULL); +- if (ret) { +- dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", +- irq, ret); +- return ret; +- } +- + /* Initialize the channels. */ + INIT_LIST_HEAD(&dmac->engine.channels); + +@@ -916,6 +900,21 @@ static int rz_dmac_probe(struct platform_device *pdev) + goto err; + } + ++ /* Register interrupt handler for error */ ++ irq = platform_get_irq_byname(pdev, irqname); ++ if (irq < 0) { ++ ret = irq; ++ goto err; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, ++ irqname, NULL); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", ++ irq, ret); ++ goto err; ++ } ++ + /* Register the DMAC as a DMA provider for DT. */ + ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate, + NULL); +@@ -954,16 +953,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + dma_register_err: + of_dma_controller_free(pdev->dev.of_node); + err: +- channel_num = i ? i - 1 : 0; +- for (i = 0; i < channel_num; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } +- + pm_runtime_put(&pdev->dev); + err_pm_disable: + pm_runtime_disable(&pdev->dev); +@@ -974,16 +963,7 @@ static int rz_dmac_probe(struct platform_device *pdev) + static int rz_dmac_remove(struct platform_device *pdev) + { + struct rz_dmac *dmac = platform_get_drvdata(pdev); +- unsigned int i; +- +- for (i = 0; i < dmac->n_channels; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; + +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&dmac->engine); + pm_runtime_put(&pdev->dev); +-- +2.53.0 + diff --git a/queue-6.1/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch b/queue-6.1/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch new file mode 100644 index 0000000000..1f60f815c3 --- /dev/null +++ b/queue-6.1/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch @@ -0,0 +1,43 @@ +From b369d91714fdd39f7b8d17c5bc8125421a79bdc2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 3 Jun 2026 17:37:49 +0900 +Subject: gpu: host1x: Fix use-after-free in host1x_bo_clear_cached_mappings + +From: Mikko Perttunen + +[ Upstream commit 266cddf7bd0f6c79b6c0633aef742a22bf70265b ] + +__host1x_bo_unpin() drops the last reference to the mapping and frees +it, so we can't dereference mapping afterwards. The cache itself +outlives the mapping, so use the cache local variable instead. + +Reported-by: Dan Carpenter +Closes: https://lore.kernel.org/linux-tegra/ah6ErK6f4kVudVIA@stanley.mountain/T/#u +Signed-off-by: Mikko Perttunen +Signed-off-by: Thierry Reding +Link: https://patch.msgid.link/20260603-host1x-bocache-leak-fix-v1-1-494101dbfd30@nvidia.com +Signed-off-by: Sasha Levin +--- + drivers/gpu/host1x/bus.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c +index 819f0c4fdfa899..8c819af1bce697 100644 +--- a/drivers/gpu/host1x/bus.c ++++ b/drivers/gpu/host1x/bus.c +@@ -1025,10 +1025,10 @@ void host1x_bo_clear_cached_mappings(struct host1x_bo *bo) + if (WARN_ON(!cache)) + continue; + +- mutex_lock(&mapping->cache->lock); ++ mutex_lock(&cache->lock); + WARN_ON(kref_read(&mapping->ref) != 1); + __host1x_bo_unpin(&mapping->ref); +- mutex_unlock(&mapping->cache->lock); ++ mutex_unlock(&cache->lock); + } + } + EXPORT_SYMBOL(host1x_bo_clear_cached_mappings); +-- +2.53.0 + diff --git a/queue-6.1/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch b/queue-6.1/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch new file mode 100644 index 0000000000..485bf163d4 --- /dev/null +++ b/queue-6.1/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch @@ -0,0 +1,114 @@ +From 01c87ba3a5c53eb29afea74dd476a7df9614dd66 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 8 Apr 2026 09:03:59 -0700 +Subject: Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data() + +From: Seungjin Bae + +[ Upstream commit 875115b82c295277b81b6dfee7debc725f44e854 ] + +The `ims_pcu_process_data()` processes incoming URB data byte by byte. +However, it fails to check if the `read_pos` index exceeds +IMS_PCU_BUF_SIZE. + +If a malicious USB device sends a packet larger than IMS_PCU_BUF_SIZE, +`read_pos` will increment indefinitely. Moreover, since `read_pos` is +located immediately after `read_buf`, the attacker can overwrite +`read_pos` itself to arbitrarily control the index. + +This manipulated `read_pos` is subsequently used in +`ims_pcu_handle_response()` to copy data into `cmd_buf`, leading to a +heap buffer overflow. + +Specifically, an attacker can overwrite the `cmd_done.wait.head` located +at offset 136 relative to `cmd_buf` in the `ims_pcu_handle_response()`. +Consequently, when the driver calls `complete(&pcu->cmd_done)`, it +triggers a control flow hijack by using the manipulated pointer. + +Fix this by adding a bounds check for `read_pos` before writing to +`read_buf`. If the packet is too long, discard it, log a warning, +and reset the parser state. + +Fixes: 628329d524743 ("Input: add IMS Passenger Control Unit driver") +Co-developed-by: Sanghoon Choi +Signed-off-by: Sanghoon Choi +Signed-off-by: Seungjin Bae +Link: https://patch.msgid.link/20251221211442.841549-2-eeodqql09@gmail.com +[dtor: factor out resetting packet state, reset checksum as well] +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 32 ++++++++++++++++++++++++++------ + 1 file changed, 26 insertions(+), 6 deletions(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 2bac9d9c7b0c9c..af1dfc08b10b25 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -448,6 +448,14 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + } + } + ++static void ims_pcu_reset_packet(struct ims_pcu *pcu) ++{ ++ pcu->have_stx = true; ++ pcu->have_dle = false; ++ pcu->read_pos = 0; ++ pcu->check_sum = 0; ++} ++ + static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + { + int i; +@@ -460,6 +468,14 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + continue; + + if (pcu->have_dle) { ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->have_dle = false; + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; +@@ -472,10 +488,8 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + dev_warn(pcu->dev, + "Unexpected STX at byte %d, discarding old data\n", + pcu->read_pos); ++ ims_pcu_reset_packet(pcu); + pcu->have_stx = true; +- pcu->have_dle = false; +- pcu->read_pos = 0; +- pcu->check_sum = 0; + break; + + case IMS_PCU_PROTOCOL_DLE: +@@ -495,12 +509,18 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + ims_pcu_handle_response(pcu); + } + +- pcu->have_stx = false; +- pcu->have_dle = false; +- pcu->read_pos = 0; ++ ims_pcu_reset_packet(pcu); + break; + + default: ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + break; +-- +2.53.0 + diff --git a/queue-6.1/input-ims-pcu-fix-logic-error-in-packet-reset.patch b/queue-6.1/input-ims-pcu-fix-logic-error-in-packet-reset.patch new file mode 100644 index 0000000000..fd40432431 --- /dev/null +++ b/queue-6.1/input-ims-pcu-fix-logic-error-in-packet-reset.patch @@ -0,0 +1,42 @@ +From 5bcff697c8f7963a9a54dddea59b9c232d40fbab Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 22 May 2026 10:29:41 -0700 +Subject: Input: ims-pcu - fix logic error in packet reset + +From: Dmitry Torokhov + +[ Upstream commit 2c9b85a14abb4811e8d4773ccd13559e59792efb ] + +ims_pcu_reset_packet() incorrectly sets have_stx to true, which implies +that the start-of-packet delimiter has already been received. This +causes the protocol parser to skip waiting for the next STX byte and +potentially process garbage data. + +Correctly set have_stx to false when resetting the packet state. + +Fixes: 875115b82c29 ("Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data()") +Cc: stable@vger.kernel.org +Reported-by: Sashiko bot +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index af1dfc08b10b25..6b2aeaa50812f2 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -450,7 +450,7 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + + static void ims_pcu_reset_packet(struct ims_pcu *pcu) + { +- pcu->have_stx = true; ++ pcu->have_stx = false; + pcu->have_dle = false; + pcu->read_pos = 0; + pcu->check_sum = 0; +-- +2.53.0 + diff --git a/queue-6.1/series b/queue-6.1/series index 46ac8f8c14..37eee48b05 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -7,3 +7,18 @@ seqlock-allow-ubsan_alignment-to-fail-optimizing.patch kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch kvm-x86-mmu-fix-use-after-free-on-vendor-module-reload.patch +can-bcm-add-locking-when-updating-filter-and-timer-v.patch +can-bcm-fix-can-frame-rx-tx-statistics.patch +can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch +can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch +can-bcm-add-missing-device-refcount-for-can-filter-r.patch +can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch +can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch +can-bcm-track-a-single-source-interface-for-anydev-t.patch +can-isotp-fix-use-after-free-race-with-concurrent-ne.patch +can-isotp-serialize-tx-state-transitions-under-so-rx.patch +dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch +gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch +xprtrdma-clear-receive-side-ownership-pointers-on-re.patch +input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch +input-ims-pcu-fix-logic-error-in-packet-reset.patch diff --git a/queue-6.1/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch b/queue-6.1/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch new file mode 100644 index 0000000000..5407cc59ba --- /dev/null +++ b/queue-6.1/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch @@ -0,0 +1,110 @@ +From abb66b17cbca62dc3cf93d60725f5d40f9cae71b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 10:14:04 -0400 +Subject: xprtrdma: Clear receive-side ownership pointers on release + +From: Chuck Lever + +[ Upstream commit 2ae8e7afbc63bf84243367f89eb43571f0345a74 ] + +Three small ownership-state cleanups land the transport in a +state that lets future reviewers reason about each pointer +locally rather than tracing the whole reply path: + +rpcrdma_rep_put() clears rep->rr_rqst before the rep enters +rb_free_reps so that no rep on the free list still carries a +stale rqst pointer. rpcrdma_reply_handler() and +rpcrdma_unpin_rqst() are the only sites that set rr_rqst; +rpcrdma_reply_handler() hands the rep through +rpcrdma_rep_put(), and rpcrdma_unpin_rqst() NULLs rr_rqst +directly because its error path abandons the rep for +teardown cleanup rather than returning it to rb_free_reps. + +rpcrdma_reply_put() NULLs req->rl_reply before calling +rpcrdma_rep_put(). The previous order placed the rep on +rb_free_reps while req->rl_reply still pointed at it; the +window was harmless because xprt_rdma_free_slot() holds the +req exclusively across the pair, but closing it makes the +invariant 'rep on rb_free_reps implies no req references it' +strictly checkable. + +rpcrdma_sendctx_unmap() and rpcrdma_sendctx_cancel() clear +req->rl_sendctx after dropping the sendctx pointer in the +sendctx ring. Without this, req->rl_sendctx survives across +Send completion and points at a sendctx that may already have +been reassigned by rpcrdma_sendctx_get_locked() to a different +req. No caller dereferences the stale pointer today -- +rpcrdma_prepare_send_sges() overwrites it before the next +Send -- but a NULL is a more honest representation of 'the +Send is no longer outstanding' and lets the assertion patch +that follows trip on any future regression. + +Signed-off-by: Chuck Lever +Signed-off-by: Anna Schumaker +Signed-off-by: Sasha Levin +--- + net/sunrpc/xprtrdma/rpc_rdma.c | 4 ++++ + net/sunrpc/xprtrdma/verbs.c | 12 ++++++++++-- + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c +index e201b37578a70e..aa57e057ff451f 100644 +--- a/net/sunrpc/xprtrdma/rpc_rdma.c ++++ b/net/sunrpc/xprtrdma/rpc_rdma.c +@@ -542,6 +542,7 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + rpcrdma_req_put(req); + } + +@@ -550,8 +551,11 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + */ + static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) + { ++ struct rpcrdma_req *req = sc->sc_req; ++ + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + } + + /* Prepare an SGE for the RPC-over-RDMA transport header. +diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c +index 27bb176f082f63..a97f0b18ac4294 100644 +--- a/net/sunrpc/xprtrdma/verbs.c ++++ b/net/sunrpc/xprtrdma/verbs.c +@@ -1067,9 +1067,15 @@ static struct rpcrdma_rep *rpcrdma_rep_get_locked(struct rpcrdma_buffer *buf) + * @buf: buffer pool + * @rep: rep to release + * ++ * The rep's transient association with an rpc_rqst, established ++ * by rpcrdma_reply_handler() and torn down here, must not survive ++ * onto rb_free_reps: rpcrdma_post_recvs() pulls reps from the free ++ * list to re-post them, and a non-NULL rr_rqst on a free-listed rep ++ * would imply the rep is still referenced by a req. + */ + void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep) + { ++ rep->rr_rqst = NULL; + llist_add(&rep->rr_node, &buf->rb_free_reps); + } + +@@ -1252,9 +1258,11 @@ rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt) + */ + void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) + { +- if (req->rl_reply) { +- rpcrdma_rep_put(buffers, req->rl_reply); ++ struct rpcrdma_rep *rep = req->rl_reply; ++ ++ if (rep) { + req->rl_reply = NULL; ++ rpcrdma_rep_put(buffers, rep); + } + /* I2: rl_reply NULL after the put closes the + * 'rep on rb_free_reps still referenced by req' window. +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-add-locking-when-updating-filter-and-timer-v.patch b/queue-6.12/can-bcm-add-locking-when-updating-filter-and-timer-v.patch new file mode 100644 index 0000000000..3435296ad8 --- /dev/null +++ b/queue-6.12/can-bcm-add-locking-when-updating-filter-and-timer-v.patch @@ -0,0 +1,417 @@ +From 3c02aa7624ea28890eb419335468bbe564358f77 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:06 +0200 +Subject: can: bcm: add locking when updating filter and timer values + +From: Oliver Hartkopp + +commit 749179c2e25b95d22499ed29096b3e02d6dfd2b4 upstream. + +KCSAN detected a simultaneous access to timer values that can be +overwritten in bcm_rx_setup() when updating timer and filter content +while bcm_rx_handler(), bcm_rx_timeout_handler() or bcm_rx_thr_handler() +run concurrently on incoming CAN traffic. + +Protect the timer (ival1/ival2/kt_ival1/kt_ival2/kt_lastmsg) and filter +(nframes/flags/frames/last_frames) updates in bcm_rx_setup() with a new +per-op bcm_rx_update_lock, taken with the matching scope in the RX +handlers. memcpy_from_msg() is staged into a temporary buffer before the +lock is taken, since it can sleep and must not run under a spinlock. + +hrtimer_cancel() is always called without bcm_rx_update_lock held, since +bcm_rx_timeout_handler()/bcm_rx_thr_handler() take the same lock and a +running callback would otherwise deadlock against the canceller. + +Also close a related race: bcm_rx_setup() cleared the RTR flag in the +stored reply frame's can_id as a separate, unprotected step after the +frame content was already installed, so a concurrent bcm_rx_handler() +could transmit a stale reply with CAN_RTR_FLAG still set. Fold that +normalization into the initial frame preparation instead (on the staged +buffer for updates, directly on op->frames pre-registration for new +ops), so the installed frame is always atomically self-consistent. + +bcm_rx_handler()'s RX_RTR_FRAME check now takes a lock-protected +snapshot of op->flags before deciding whether to call bcm_can_tx(), +but does not hold the lock across that call. + +Also take a lock-protected snapshot of the currframe in bcm_can_tx() +to avoid partly overwrites by content updates in bcm_tx_setup(). +Finally check if a TX_RESET_MULTI_IDX/SETTIMER might have reset +op->currframe between the two locked sections in bcm_can_tx(). + +Omit calling hrtimer_forward() with zero interval in bcm_rx_thr_handler(). +kt_ival2 may have been concurrently cleared by bcm_rx_setup() before it +cancels this timer, so check kt_ival2 inside the bcm_rx_update_lock. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Reported-by: syzbot+75e5e4ae00c3b4bb544e@syzkaller.appspotmail.com +Closes: https://lore.kernel.org/linux-can/6975d5cf.a00a0220.33ccc7.0022.GAE@google.com/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 176 ++++++++++++++++++++++++++++++++++++++------------ + 1 file changed, 133 insertions(+), 43 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 03626d1ee665f1..388720ae177213 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -128,6 +128,7 @@ struct bcm_op { + struct sock *sk; + struct net_device *rx_reg_dev; + spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + + struct bcm_sock { +@@ -292,21 +293,27 @@ static int bcm_proc_show(struct seq_file *m, void *v) + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op + */ +-static void bcm_can_tx(struct bcm_op *op) ++static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + { + struct sk_buff *skb; + struct net_device *dev; +- struct canfd_frame *cf; ++ struct canfd_frame cframe; ++ bool cyclic = !cf; ++ unsigned int idx = 0; + int err; + + /* no target device? => exit */ + if (!op->ifindex) + return; + +- /* read currframe under lock protection */ +- spin_lock_bh(&op->bcm_tx_lock); +- cf = op->frames + op->cfsiz * op->currframe; +- spin_unlock_bh(&op->bcm_tx_lock); ++ if (cyclic) { ++ /* read currframe under lock protection */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ idx = op->currframe; ++ memcpy(&cframe, op->frames + op->cfsiz * idx, op->cfsiz); ++ cf = &cframe; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ } + + dev = dev_get_by_index(sock_net(op->sk), op->ifindex); + if (!dev) { +@@ -335,14 +342,20 @@ static void bcm_can_tx(struct bcm_op *op) + if (!err) + op->frames_abs++; + +- op->currframe++; ++ /* only advance the cyclic sequence if nothing reset currframe while ++ * we were sending - a concurrent TX_RESET_MULTI_IDX means this ++ * frame's bookkeeping belongs to a sequence that no longer exists ++ */ ++ if (!cyclic || op->currframe == idx) { ++ op->currframe++; + +- /* reached last frame? */ +- if (op->currframe >= op->nframes) +- op->currframe = 0; ++ /* reached last frame? */ ++ if (op->currframe >= op->nframes) ++ op->currframe = 0; + +- if (op->count > 0) +- op->count--; ++ if (op->count > 0) ++ op->count--; ++ } + + spin_unlock_bh(&op->bcm_tx_lock); + out: +@@ -455,7 +468,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_msg_head msg_head; + + if (op->kt_ival1 && (op->count > 0)) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + if (!op->count && (op->flags & TX_COUNTEVT)) { + + /* create notification to user */ +@@ -472,7 +485,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + } + + } else if (op->kt_ival2) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + } + + return bcm_tx_set_expiry(op, &op->timer) ? +@@ -616,6 +629,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + /* if user wants to be informed, when cyclic CAN-Messages come back */ + if ((op->flags & RX_ANNOUNCE_RESUME) && op->last_frames) { + /* clear received CAN frames to indicate 'nothing received' */ +@@ -632,6 +647,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ + bcm_send_to_user(op, &msg_head, NULL, 0); + + return HRTIMER_NORESTART; +@@ -680,15 +697,26 @@ static int bcm_rx_thr_flush(struct bcm_op *op) + static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer); ++ enum hrtimer_restart ret; + +- if (bcm_rx_thr_flush(op)) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ /* kt_ival2 may have been concurrently cleared by bcm_rx_setup() ++ * before it cancels this timer - never forward with a zero ++ * interval in that case. ++ */ ++ if (bcm_rx_thr_flush(op) && op->kt_ival2) { + hrtimer_forward_now(hrtimer, op->kt_ival2); +- return HRTIMER_RESTART; ++ ret = HRTIMER_RESTART; + } else { + /* rearm throttle handling */ + op->kt_lastmsg = 0; +- return HRTIMER_NORESTART; ++ ret = HRTIMER_NORESTART; + } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ return ret; + } + + /* +@@ -698,8 +726,10 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + { + struct bcm_op *op = (struct bcm_op *)data; + const struct canfd_frame *rxframe = (struct canfd_frame *)skb->data; ++ struct canfd_frame rtrframe; + unsigned int i; + unsigned char traffic_flags; ++ bool rtr_frame; + + if (op->can_id != rxframe->can_id) + return; +@@ -723,9 +753,18 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* update statistics */ + op->frames_abs++; + +- if (op->flags & RX_RTR_FRAME) { ++ /* snapshot the flag under lock: op->flags/op->frames may be updated ++ * concurrently by bcm_rx_setup(). ++ */ ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ rtr_frame = op->flags & RX_RTR_FRAME; ++ if (rtr_frame) ++ memcpy(&rtrframe, op->frames, op->cfsiz); ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ +- bcm_can_tx(op); ++ bcm_can_tx(op, &rtrframe); + return; + } + +@@ -737,6 +776,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe, +@@ -772,6 +813,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + + rx_starttimer: + bcm_rx_starttimer(op); ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + /* +@@ -1115,7 +1158,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + list_add_rcu(&op->list, &bo->tx_ops); + + if (op->flags & TX_ANNOUNCE) +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + + if (op->flags & STARTTIMER) + bcm_tx_start_timer(op); +@@ -1129,6 +1172,24 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + ++static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) ++{ ++ /* funny feature in RX(!)_SETUP only for RTR-mode: ++ * copy can_id into frame BUT without RTR-flag to ++ * prevent a full-load-loopback-test ... ;-] ++ * normalize this on the staged buffer, before it is ++ * ever installed into op->frames. ++ */ ++ if (msg_head->flags & RX_RTR_FRAME) { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; ++ } ++} ++ + /* + * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg) + */ +@@ -1163,6 +1224,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->rx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames = NULL; ++ + /* update existing BCM operation */ + + /* +@@ -1174,19 +1237,48 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -E2BIG; + + if (msg_head->nframes) { +- /* update CAN frames content */ +- err = memcpy_from_msg(op->frames, msg, ++ /* get new CAN frames content before locking */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, ++ GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ ++ err = memcpy_from_msg(new_frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + +- /* clear last_frames to indicate 'nothing received' */ +- memset(op->last_frames, 0, msg_head->nframes * op->cfsiz); ++ bcm_rx_setup_rtr_check(msg_head, op, new_frames); + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); + op->nframes = msg_head->nframes; + op->flags = msg_head->flags; + ++ if (msg_head->nframes) { ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, ++ msg_head->nframes * op->cfsiz); ++ ++ /* clear last_frames to indicate 'nothing received' */ ++ memset(op->last_frames, 0, ++ msg_head->nframes * op->cfsiz); ++ } ++ ++ if (msg_head->flags & SETTIMER) { ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ } ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ /* free temporary frames / kfree(NULL) is safe */ ++ kfree(new_frames); ++ + /* Only an update -> do not call can_rx_register() */ + do_rx_register = 0; + +@@ -1197,6 +1289,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -ENOMEM; + + spin_lock_init(&op->bcm_tx_lock); ++ spin_lock_init(&op->bcm_rx_update_lock); + op->can_id = msg_head->can_id; + op->nframes = msg_head->nframes; + op->cfsiz = CFSIZ(msg_head->flags); +@@ -1238,6 +1331,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + kfree(op); + return err; + } ++ ++ bcm_rx_setup_rtr_check(msg_head, op, op->frames); + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1265,29 +1360,22 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check flags */ + + if (op->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = op->frames; +- + /* no timers in RTR-mode */ + hrtimer_cancel(&op->thrtimer); + hrtimer_cancel(&op->timer); +- +- /* +- * funny feature in RX(!)_SETUP only for RTR-mode: +- * copy can_id into frame BUT without RTR-flag to +- * prevent a full-load-loopback-test ... ;-] +- */ +- if ((op->flags & TX_CP_CAN_ID) || +- (frame0->can_id == op->can_id)) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- + } else { + if (op->flags & SETTIMER) { + +- /* set timer value */ +- op->ival1 = msg_head->ival1; +- op->ival2 = msg_head->ival2; +- op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); +- op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ /* set timers (locked) for newly created op */ ++ if (do_rx_register) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } + + /* disable an active timer due to zero value? */ + if (!op->kt_ival1) +@@ -1297,9 +1385,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + * In any case cancel the throttle timer, flush + * potentially blocked msgs and reset throttle handling + */ +- op->kt_lastmsg = 0; + hrtimer_cancel(&op->thrtimer); ++ ++ spin_lock_bh(&op->bcm_rx_update_lock); + bcm_rx_thr_flush(op); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + if ((op->flags & STARTTIMER) && op->kt_ival1) +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-add-missing-device-refcount-for-can-filter-r.patch b/queue-6.12/can-bcm-add-missing-device-refcount-for-can-filter-r.patch new file mode 100644 index 0000000000..9cef8e80c2 --- /dev/null +++ b/queue-6.12/can-bcm-add-missing-device-refcount-for-can-filter-r.patch @@ -0,0 +1,121 @@ +From eadb23cf2f1aca55ded40ce32e97e86302b222e2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:10 +0200 +Subject: can: bcm: add missing device refcount for CAN filter removal + +From: Oliver Hartkopp + +commit d59948293ea34b6337ce2b5febab8510de70048c upstream. + +sashiko-bot remarked a problem with a concurrent device unregistration +in isotp.c which also is present in the bcm.c code. A former fix for raw.c +commit c275a176e4b6 ("can: raw: add missing refcount for memory leak fix") +introduced a netdevice_tracker which solves the issue for bcm.c too. + +bcm_release(), bcm_delete_rx_op() and bcm_notifier() relied on +dev_get_by_index(ifindex) to re-find the device for an rx_op before +unregistering its filter. If a concurrent NETDEV_UNREGISTER has already +unlisted the device from the ifindex table, that lookup fails and +can_rx_unregister() is silently skipped, leaving a stale CAN filter +pointing at the soon-to-be-freed bcm_op/socket. + +Hold a netdev_hold()/netdev_put() tracked reference on op->rx_reg_dev +from the moment the rx filter is registered in bcm_rx_setup() until it +is unregistered in bcm_rx_unreg(), and use that reference directly in +bcm_release() and bcm_delete_rx_op() instead of re-looking the device +up by ifindex. + +Reported-by: sashiko-bot@kernel.org +Closes: https://sashiko.dev/#/patchset/20260707094716.63578-1-socketcan@hartkopp.net +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 44 ++++++++++++++++++++++++-------------------- + 1 file changed, 24 insertions(+), 20 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 2c12a5441eac48..e0aacfdcad8864 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -931,6 +931,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op) + + /* mark as removed subscription */ + op->rx_reg_dev = NULL; ++ dev_put(dev); + } else + printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device " + "mismatch %p %p\n", op->rx_reg_dev, dev); +@@ -961,17 +962,14 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh, + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the dev. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(sock_net(op->sk), +- op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(sock_net(op->sk), NULL, + op->can_id, +@@ -1494,7 +1492,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + bcm_rx_handler, op, + "bcm", sk); + +- op->rx_reg_dev = dev; ++ /* keep a reference so that a later ++ * unregister can safely reach the device even ++ * if a concurrent NETDEV_UNREGISTER has ++ * already unlisted it by ifindex ++ */ ++ if (!err) { ++ op->rx_reg_dev = dev; ++ dev_hold(dev); ++ } + dev_put(dev); + } else { + /* the requested device is gone - do not +@@ -1867,16 +1873,14 @@ static int bcm_release(struct socket *sock) + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the device. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(net, op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(net, NULL, op->can_id, + REGMASK(op->can_id), +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch b/queue-6.12/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch new file mode 100644 index 0000000000..3a19f6fbdb --- /dev/null +++ b/queue-6.12/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch @@ -0,0 +1,230 @@ +From 7ffc58fcc2e3c75084d6149dc16541c7a5f373ac Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:08 +0200 +Subject: can: bcm: extend bcm_tx_lock usage for data and timer updates + +From: Oliver Hartkopp + +commit 12ce799f7ab1e05bd8fbf79e46f403bfe5597ebc upstream. + +Stage new CAN frame content for an existing tx op into a kmalloc()'d +buffer and validate it there, mirroring the approach already used in +bcm_rx_setup(). Only copy the validated data into op->frames while +holding op->bcm_tx_lock, so bcm_can_tx() and bcm_tx_timeout_handler() +can no longer observe a partially updated or unvalidated frame. + +Add a missing error path for memcpy_from_msg() when copying CAN frame +data from userspace. + +Also move the kt_ival1/kt_ival2/ival1/ival2 updates in bcm_tx_setup() +under op->bcm_tx_lock, and read kt_ival1/kt_ival2/count under the same +lock in bcm_tx_set_expiry() and bcm_tx_timeout_handler(), closing the +torn 64-bit ktime_t read on 32-bit platforms. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 104 ++++++++++++++++++++++++++++++++++++-------------- + 1 file changed, 75 insertions(+), 29 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 9982f1b9ac4620..e8fb31b24bb2ae 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -127,7 +127,7 @@ struct bcm_op { + struct canfd_frame last_sframe; + struct sock *sk; + struct net_device *rx_reg_dev; +- spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_tx_lock; /* protect tx data and timer updates */ + spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + +@@ -466,12 +466,18 @@ static bool bcm_tx_set_expiry(struct bcm_op *op, struct hrtimer *hrt) + { + ktime_t ival; + ++ spin_lock_bh(&op->bcm_tx_lock); ++ + if (op->kt_ival1 && op->count) + ival = op->kt_ival1; +- else if (op->kt_ival2) ++ else if (op->kt_ival2) { + ival = op->kt_ival2; +- else ++ } else { ++ spin_unlock_bh(&op->bcm_tx_lock); + return false; ++ } ++ ++ spin_unlock_bh(&op->bcm_tx_lock); + + hrtimer_set_expires(hrt, ktime_add(ktime_get(), ival)); + return true; +@@ -488,25 +494,47 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; ++ bool tx_ival1, tx_ival2; ++ ++ /* snapshot kt_ival1/kt_ival2/count under lock to avoid torn ++ * ktime_t reads racing with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ tx_ival1 = op->kt_ival1 && (op->count > 0); ++ tx_ival2 = !!op->kt_ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (tx_ival1) { ++ u32 flags, count; ++ struct bcm_timeval ival1, ival2; + +- if (op->kt_ival1 && (op->count > 0)) { + bcm_can_tx(op, NULL); +- if (!op->count && (op->flags & TX_COUNTEVT)) { + ++ /* snapshot variables under lock to avoid torn reads racing ++ * with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ flags = op->flags; ++ count = op->count; ++ ival1 = op->ival1; ++ ival2 = op->ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (!count && (flags & TX_COUNTEVT)) { + /* create notification to user */ + memset(&msg_head, 0, sizeof(msg_head)); + msg_head.opcode = TX_EXPIRED; +- msg_head.flags = op->flags; +- msg_head.count = op->count; +- msg_head.ival1 = op->ival1; +- msg_head.ival2 = op->ival2; ++ msg_head.flags = flags; ++ msg_head.count = count; ++ msg_head.ival1 = ival1; ++ msg_head.ival2 = ival2; + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + + bcm_send_to_user(op, &msg_head, NULL, 0); + } + +- } else if (op->kt_ival2) { ++ } else if (tx_ival2) { + bcm_can_tx(op, NULL); + } + +@@ -1030,6 +1058,8 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->tx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames; ++ + /* update existing BCM operation */ + + /* +@@ -1040,11 +1070,23 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes > op->nframes) + return -E2BIG; + +- /* update CAN frames content */ ++ /* get new CAN frames content into a staging buffer before ++ * locking: validate and normalize the frames there so that ++ * bcm_can_tx() / bcm_tx_timeout_handler() never observe a ++ * partially updated or unvalidated frame in op->frames ++ */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ + for (i = 0; i < msg_head->nframes; i++) { + +- cf = op->frames + op->cfsiz * i; ++ cf = new_frames + op->cfsiz * i; + err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + + if (op->flags & CAN_FD_FRAME) { + if (cf->len > 64) +@@ -1054,36 +1096,38 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -EINVAL; + } + +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + + if (msg_head->flags & TX_CP_CAN_ID) { + /* copy can_id into frame */ + cf->can_id = msg_head->can_id; + } + } ++ ++ spin_lock_bh(&op->bcm_tx_lock); ++ ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, msg_head->nframes * op->cfsiz); ++ + op->flags = msg_head->flags; + +- /* only lock for unlikely count/nframes/currframe changes */ + if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX || +- op->flags & SETTIMER) { +- +- spin_lock_bh(&op->bcm_tx_lock); ++ op->flags & TX_RESET_MULTI_IDX) { ++ /* potentially update changed nframes */ ++ op->nframes = msg_head->nframes; ++ /* restart multiple frame transmission */ ++ op->currframe = 0; ++ } + +- if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX) { +- /* potentially update changed nframes */ +- op->nframes = msg_head->nframes; +- /* restart multiple frame transmission */ +- op->currframe = 0; +- } ++ if (op->flags & SETTIMER) ++ op->count = msg_head->count; + +- if (op->flags & SETTIMER) +- op->count = msg_head->count; ++ spin_unlock_bh(&op->bcm_tx_lock); + +- spin_unlock_bh(&op->bcm_tx_lock); +- } ++ kfree(new_frames); + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1160,10 +1204,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + if (op->flags & SETTIMER) { + /* set timer values */ ++ spin_lock_bh(&op->bcm_tx_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ spin_unlock_bh(&op->bcm_tx_lock); + + /* disable an active timer due to zero values? */ + if (!op->kt_ival1 && !op->kt_ival2) +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-fix-can-frame-rx-tx-statistics.patch b/queue-6.12/can-bcm-fix-can-frame-rx-tx-statistics.patch new file mode 100644 index 0000000000..ab8df413c2 --- /dev/null +++ b/queue-6.12/can-bcm-fix-can-frame-rx-tx-statistics.patch @@ -0,0 +1,200 @@ +From c1ff924a9a9843b585593211f1c40c1ef2b72cf5 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:07 +0200 +Subject: can: bcm: fix CAN frame rx/tx statistics + +From: Oliver Hartkopp + +commit e6c24ba95fc3f1b5e1dcd28b1c6e59ef61a9daa5 upstream. + +KCSAN detected a data race within the bcm_rx_handler() when two CAN frames +have been simultaneously received and processed in a single rx op by two +different CPUs. + +Use atomic operations with (signed) long data types to access the +statistics in the hot path to fix the KCSAN complaint. + +Additionally simplify the update and check of statistics overflow by +using the atomic operations in separate bcm_update_[rx|tx]_stats() +functions. The rx variant runs under bcm_rx_update_lock to prevent +races when resetting the two rx counters; the tx variant runs under +bcm_tx_lock and only needs to guard its own counter's overflow. + +As the rx path resets its values already at LONG_MAX / 100, there is +no conflict between the two locking domains (bcm_rx_update_lock vs. +bcm_tx_lock) even for ops that use both paths. + +The rx statistics update and the frames_filtered update in +bcm_rx_changed() were previously performed in two separate +bcm_rx_update_lock sections. For an rx op subscribed on all interfaces +(ifindex == 0), bcm_rx_handler() can run concurrently on different +CPUs, so a counter reset by one CPU between these two sections could +leave frames_filtered larger than frames_abs on another CPU, producing +a bogus (even negative) reduction percentage in procfs. Update the +statistics in the same critical section as bcm_rx_changed() to close +this gap, which also removes the now unneeded extra lock/unlock pair +around the traffic_flags calculation. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 69 ++++++++++++++++++++++++++++++++++----------------- + 1 file changed, 46 insertions(+), 23 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 388720ae177213..9982f1b9ac4620 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -111,7 +111,7 @@ struct bcm_op { + int ifindex; + canid_t can_id; + u32 flags; +- unsigned long frames_abs, frames_filtered; ++ atomic_long_t frames_abs, frames_filtered; + struct bcm_timeval ival1, ival2; + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; +@@ -228,10 +228,13 @@ static int bcm_proc_show(struct seq_file *m, void *v) + + list_for_each_entry_rcu(op, &bo->rx_ops, list) { + +- unsigned long reduction; ++ long reduction, frames_filtered, frames_abs; ++ ++ frames_filtered = atomic_long_read(&op->frames_filtered); ++ frames_abs = atomic_long_read(&op->frames_abs); + + /* print only active entries & prevent division by zero */ +- if (!op->frames_abs) ++ if (!frames_abs) + continue; + + seq_printf(m, "rx_op: %03X %-5s ", op->can_id, +@@ -253,9 +256,9 @@ static int bcm_proc_show(struct seq_file *m, void *v) + (long long)ktime_to_us(op->kt_ival2)); + + seq_printf(m, "# recv %ld (%ld) => reduction: ", +- op->frames_filtered, op->frames_abs); ++ frames_filtered, frames_abs); + +- reduction = 100 - (op->frames_filtered * 100) / op->frames_abs; ++ reduction = 100 - (frames_filtered * 100) / frames_abs; + + seq_printf(m, "%s%ld%%\n", + (reduction == 100) ? "near " : "", reduction); +@@ -279,7 +282,8 @@ static int bcm_proc_show(struct seq_file *m, void *v) + seq_printf(m, "t2=%lld ", + (long long)ktime_to_us(op->kt_ival2)); + +- seq_printf(m, "# sent %ld\n", op->frames_abs); ++ seq_printf(m, "# sent %ld\n", ++ atomic_long_read(&op->frames_abs)); + } + seq_putc(m, '\n'); + +@@ -289,6 +293,24 @@ static int bcm_proc_show(struct seq_file *m, void *v) + } + #endif /* CONFIG_PROC_FS */ + ++static void bcm_update_rx_stats(struct bcm_op *op) ++{ ++ /* prevent overflow of the reduction% calculation in bcm_proc_show() */ ++ if (atomic_long_inc_return(&op->frames_abs) > LONG_MAX / 100) { ++ atomic_long_set(&op->frames_filtered, 0); ++ atomic_long_set(&op->frames_abs, 0); ++ } ++} ++ ++static void bcm_update_tx_stats(struct bcm_op *op) ++{ ++ /* tx_op has no reduction% calculation - use the full range and ++ * just keep the displayed counter non-negative on overflow ++ */ ++ if (atomic_long_inc_return(&op->frames_abs) == LONG_MAX) ++ atomic_long_set(&op->frames_abs, 0); ++} ++ + /* + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op +@@ -340,7 +362,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + spin_lock_bh(&op->bcm_tx_lock); + + if (!err) +- op->frames_abs++; ++ bcm_update_tx_stats(op); + + /* only advance the cyclic sequence if nothing reset currframe while + * we were sending - a concurrent TX_RESET_MULTI_IDX means this +@@ -499,12 +521,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data) + { + struct bcm_msg_head head; + +- /* update statistics */ +- op->frames_filtered++; +- +- /* prevent statistics overflow */ +- if (op->frames_filtered > ULONG_MAX/100) +- op->frames_filtered = op->frames_abs = 0; ++ /* update statistics (frames_filtered <= frames_abs) */ ++ if (atomic_long_read(&op->frames_abs)) ++ atomic_long_inc(&op->frames_filtered); + + /* this element is not throttled anymore */ + data->flags &= ~RX_THR; +@@ -750,24 +769,30 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + op->rx_stamp = skb->tstamp; + /* save originator for recvfrom() */ + op->rx_ifindex = skb->dev->ifindex; +- /* update statistics */ +- op->frames_abs++; + +- /* snapshot the flag under lock: op->flags/op->frames may be updated +- * concurrently by bcm_rx_setup(). +- */ ++ /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); ++ + rtr_frame = op->flags & RX_RTR_FRAME; +- if (rtr_frame) ++ if (rtr_frame) { ++ bcm_update_rx_stats(op); ++ /* snapshot RTR content under lock */ + memcpy(&rtrframe, op->frames, op->cfsiz); +- spin_unlock_bh(&op->bcm_rx_update_lock); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + +- if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ + bcm_can_tx(op, &rtrframe); + return; + } + ++ /* update statistics in the same critical section as bcm_rx_changed() ++ * below: frames_filtered must never be checked/incremented against a ++ * frames_abs snapshot from a concurrent bcm_rx_handler() call on ++ * another CPU for the same (wildcard) op, or frames_filtered can end ++ * up larger than frames_abs. ++ */ ++ bcm_update_rx_stats(op); ++ + /* compute flags to distinguish between own/local/remote CAN traffic */ + traffic_flags = 0; + if (skb->sk) { +@@ -776,8 +801,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + +- spin_lock_bh(&op->bcm_rx_update_lock); +- + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe, +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch b/queue-6.12/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch new file mode 100644 index 0000000000..8a4c187fc3 --- /dev/null +++ b/queue-6.12/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch @@ -0,0 +1,74 @@ +From 2aebcbdcb5d08eac27b766f502c2ca0e10ebe4b5 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:12 +0200 +Subject: can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler() + +From: Oliver Hartkopp + +commit 58fd6cbc8541216af1d7ed272ea7ac2b66d50fd8 upstream. + +For an rx op subscribed on all interfaces (ifindex == 0), the same op +is registered once in the shared per-netns wildcard filter list, so +bcm_rx_handler() can run concurrently on different CPUs for frames +arriving on different net devices. + +op->rx_stamp and op->rx_ifindex were written before bcm_rx_update_lock was +taken, allowing concurrent writers to race each other - including a torn +store of the 64-bit rx_stamp on 32-bit platforms. + +Beyond a torn store bcm_send_to_user() must report the timestamp/ifindex +of the very same frame whose content it is delivering. So the assignment +is placed in the same unbroken bcm_rx_update_lock section as the content +comparison. + +As a side effect, the RTR-request frame feature (which never reach +bcm_send_to_user()) no longer updates rx_stamp/rx_ifindex, since only +the notification path needs them. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260707145135.5BC831F00A3A@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index dd176d80f4cfbf..0312b69a988c5f 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -793,11 +793,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* disable timeout */ + hrtimer_cancel(&op->timer); + +- /* save rx timestamp */ +- op->rx_stamp = skb->tstamp; +- /* save originator for recvfrom() */ +- op->rx_ifindex = skb->dev->ifindex; +- + /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); + +@@ -829,6 +824,14 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + ++ /* save rx timestamp and originator for recvfrom() under lock. ++ * For an op subscribed on all interfaces (ifindex == 0) ++ * bcm_rx_handler() can run concurrently on different CPUs so ++ * the CAN content and the meta data must be bundled correctly. ++ */ ++ op->rx_stamp = skb->tstamp; ++ op->rx_ifindex = skb->dev->ifindex; ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe, +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch b/queue-6.12/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch new file mode 100644 index 0000000000..8e4803d58b --- /dev/null +++ b/queue-6.12/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch @@ -0,0 +1,164 @@ +From 331f4396db72e30bfd1e33797d933847ec61a908 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:11 +0200 +Subject: can: bcm: fix stale rx/tx ops after device removal + +From: Oliver Hartkopp + +commit 3b762c0d950383ab7a002686c9136b9aa55d2d70 upstream. + +RX: an RX_SETUP update(!) for an existing op skipped can_rx_register() +unconditionally, even when a concurrent NETDEV_UNREGISTER had already +torn down its registration (op->rx_reg_dev == NULL). This silently +did not re-enable frame delivery for that updated filter. bcm_rx_setup() +now re-registers in that case, while leaving rx_ops with ifindex = 0 +(all CAN devices) which never carry a tracked rx_reg_dev registered as-is. + +TX: bcm_notify() only handled bo->rx_ops on NETDEV_UNREGISTER, leaving +tx_ops with an active cyclic transmission re-arming its hrtimer +indefinitely to execute bcm_tx_timeout_handler(). Cancelling the hrtimer +prevents the runaway timer and any injection into a later reused ifindex, +since nothing else calls bcm_can_tx() for the op until an explicit +TX_SETUP update re-arms it. + +Unlike bcm_rx_unreg(), which clears the tracked rx_reg_dev for rx_ops, +the ifindex is intentionally left unchanged for tx_ops. bcm_tx_setup() +always rejects ifindex 0, so clearing it would strand the op: neither a +later TX_SETUP (bcm_find_op()) nor TX_DELETE (bcm_delete_tx_op()) could +ever find it again, since both require an exact ifindex match. + +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260708094536.DDF821F00A3A@smtp.kernel.org/ +Closes: https://lore.kernel.org/linux-can/20260708154039.347ED1F000E9@smtp.kernel.org/ +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 54 +++++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 44 insertions(+), 10 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index e0aacfdcad8864..dd176d80f4cfbf 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1281,6 +1281,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + struct bcm_sock *bo = bcm_sk(sk); + struct bcm_op *op; + int do_rx_register; ++ int new_op = 0; + int err = 0; + + if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) { +@@ -1365,8 +1366,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* free temporary frames / kfree(NULL) is safe */ + kfree(new_frames); + +- /* Only an update -> do not call can_rx_register() */ +- do_rx_register = 0; ++ /* Don't register a new CAN filter for the rx_op update unless ++ * a concurrent NETDEV_UNREGISTER notifier already tore down ++ * the previous registration. In this case the receiver needs ++ * to be re-registered here so that this update doesn't ++ * silently stop delivering frames for the given ifindex. ++ * Ops with ifindex = 0 (all CAN interfaces) never carry a ++ * tracked rx_reg_dev and stay registered as-is. ++ */ ++ do_rx_register = (ifindex && !op->rx_reg_dev) ? 1 : 0; + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1436,6 +1444,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + /* call can_rx_register() */ + do_rx_register = 1; ++ new_op = 1; + + } /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */ + +@@ -1449,7 +1458,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (op->flags & SETTIMER) { + + /* set timers (locked) for newly created op */ +- if (do_rx_register) { ++ if (new_op) { + spin_lock_bh(&op->bcm_rx_update_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; +@@ -1479,7 +1488,10 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + HRTIMER_MODE_REL_SOFT); + } + +- /* now we can register for can_ids, if we added a new bcm_op */ ++ /* now we can register for can_ids, if we added a new bcm_op ++ * or need to re-register after a NETDEV_UNREGISTER tore down ++ * the previous registration of an existing op ++ */ + if (do_rx_register) { + if (ifindex) { + struct net_device *dev; +@@ -1509,18 +1521,32 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -ENODEV; + } + +- } else ++ } else { + err = can_rx_register(sock_net(sk), NULL, op->can_id, + REGMASK(op->can_id), + bcm_rx_handler, op, "bcm", sk); ++ } ++ + if (err) { +- /* this bcm rx op is broken -> remove it */ +- bcm_remove_op(op); ++ /* newly created bcm rx op is broken -> remove it */ ++ if (new_op) { ++ bcm_remove_op(op); ++ return err; ++ } ++ ++ /* an existing op just stays unregistered. ++ * Cancel op->timer and (defensively) op->thrtimer. ++ * Other settings can't be reached until the next ++ * successful RX_SETUP. ++ */ ++ hrtimer_cancel(&op->timer); ++ hrtimer_cancel(&op->thrtimer); + return err; + } + +- /* add this bcm_op to the list of the rx_ops */ +- list_add_rcu(&op->list, &bo->rx_ops); ++ /* add a new bcm_op to the list of the rx_ops */ ++ if (new_op) ++ list_add_rcu(&op->list, &bo->rx_ops); + } + + return msg_head->nframes * op->cfsiz + MHSIZ; +@@ -1736,11 +1762,19 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + case NETDEV_UNREGISTER: + lock_sock(sk); + +- /* remove device specific receive entries */ ++ /* rx_ops: remove device specific receive entries */ + list_for_each_entry(op, &bo->rx_ops, list) + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* tx_ops: stop device specific cyclic transmissions on the ++ * vanishing ifindex. Cancelling the timer is enough to stop ++ * cyclic bcm_can_tx() calls as there is no re-arming. ++ */ ++ list_for_each_entry(op, &bo->tx_ops, list) ++ if (op->ifindex == dev->ifindex) ++ hrtimer_cancel(&op->timer); ++ + /* remove device reference, if this is our bound device */ + if (bo->bound && bo->ifindex == dev->ifindex) { + #if IS_ENABLED(CONFIG_PROC_FS) +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-track-a-single-source-interface-for-anydev-t.patch b/queue-6.12/can-bcm-track-a-single-source-interface-for-anydev-t.patch new file mode 100644 index 0000000000..bb78bdd5b7 --- /dev/null +++ b/queue-6.12/can-bcm-track-a-single-source-interface-for-anydev-t.patch @@ -0,0 +1,143 @@ +From ae4bcabb07eb2ef6c01a00cf68957d246a4d4deb Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:13 +0200 +Subject: can: bcm: track a single source interface for ANYDEV timeout/throttle + ops + +From: Oliver Hartkopp + +commit 2f5976f54a04e9f18b25283036ac3136be453b17 upstream. + +An ANYDEV rx op (ifindex == 0) with an active RX timeout and/or +throttle timer has no defined semantics when matching frames arrive +from several interfaces: bcm_rx_handler() can run concurrently for +the same op on different CPUs, racing hrtimer_cancel()/ +bcm_rx_starttimer() against bcm_rx_timeout_handler() and causing +spurious RX_TIMEOUT notifications and last_frames corruption. The +same concurrency lets throttled multiplex frames from different +interfaces clobber the single rx_ifindex/rx_stamp fields shared by +the op. + +Add op->if_detected to track the first interface that delivers a +matching frame while a timeout/throttle timer is configured, and +reject frames from any other interface for that op. The claim is +decided in bcm_rx_handler() before hrtimer_cancel() touches +op->timer, so a rejected frame can never disturb the claimed +interface's watchdog. RTR-mode ops are excluded via RX_RTR_FRAME, +independent of kt_ival1/kt_ival2, since those may briefly hold a +stale value from an earlier non-RTR configuration. + +The claim is released in bcm_notify() on NETDEV_UNREGISTER and in +bcm_rx_setup() when SETTIMER reconfigures the timer values. + +A (re-)claim is only possible on CAN devices in NETREG_REGISTERED +dev->reg_state to cover the release in bcm_notify() where reg_state +becomes NETREG_UNREGISTERING until synchronize_net(). + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260709105031.1A39C1F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 44 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 0312b69a988c5f..0c7e6203eb44e2 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -116,6 +116,7 @@ struct bcm_op { + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; + int rx_ifindex; ++ int if_detected; /* first received ifindex in ANYDEV rx_op mode */ + int cfsiz; + u32 count; + u32 nframes; +@@ -790,6 +791,33 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + return; + } + ++ /* An ANYDEV op with an active RX timeout and/or throttle timer ++ * tracks a single source interface: claim the first interface that ++ * delivers a matching frame and reject frames from any other one, ++ * before hrtimer_cancel() below can touch op->timer - this avoids ++ * racing bcm_rx_timeout_handler() across concurrent interfaces. ++ * RX_RTR_FRAME ops are excluded, as kt_ival1/kt_ival2 may briefly ++ * hold a stale value from an earlier non-RTR configuration. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ if (!(op->flags & RX_RTR_FRAME) && ++ (op->kt_ival1 || op->kt_ival2)) { ++ /* don't claim to vanishing interface */ ++ if (!op->if_detected && ++ skb->dev->reg_state == NETREG_REGISTERED) ++ op->if_detected = skb->dev->ifindex; ++ ++ if (op->if_detected != skb->dev->ifindex) { ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ return; ++ } ++ } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ + /* disable timeout */ + hrtimer_cancel(&op->timer); + +@@ -824,10 +852,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + +- /* save rx timestamp and originator for recvfrom() under lock. +- * For an op subscribed on all interfaces (ifindex == 0) +- * bcm_rx_handler() can run concurrently on different CPUs so +- * the CAN content and the meta data must be bundled correctly. ++ /* save rx timestamp and originator for recvfrom() under lock: an ++ * ANYDEV op without an active timer can still run concurrently on ++ * different CPUs, so content and meta data must be bundled here. + */ + op->rx_stamp = skb->tstamp; + op->rx_ifindex = skb->dev->ifindex; +@@ -1363,6 +1390,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + op->kt_lastmsg = 0; ++ op->if_detected = 0; /* reclaim ifindex in ANYDEV mode */ + } + spin_unlock_bh(&op->bcm_rx_update_lock); + +@@ -1766,10 +1794,21 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + lock_sock(sk); + + /* rx_ops: remove device specific receive entries */ +- list_for_each_entry(op, &bo->rx_ops, list) ++ list_for_each_entry(op, &bo->rx_ops, list) { + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* release an ANYDEV op's claim (see bcm_rx_handler()) ++ * on this now confirmed-gone interface. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ if (op->if_detected == dev->ifindex) ++ op->if_detected = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ } ++ + /* tx_ops: stop device specific cyclic transmissions on the + * vanishing ifindex. Cancelling the timer is enough to stop + * cyclic bcm_can_tx() calls as there is no re-arming. +-- +2.53.0 + diff --git a/queue-6.12/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch b/queue-6.12/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch new file mode 100644 index 0000000000..0ace64ee1a --- /dev/null +++ b/queue-6.12/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch @@ -0,0 +1,128 @@ +From 6dde4fedd3f9d2d02417b35eb46936371e555012 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:09 +0200 +Subject: can: bcm: validate frame length in bcm_rx_setup() for RTR replies + +From: Oliver Hartkopp + +commit 62ec41f364648be79d54d94d0d240ee326948afd upstream. + +bcm_tx_setup() validates cf->len against the CAN/CAN FD DLC limits +before installing frames for TX_SETUP, but bcm_rx_setup() never did +the same for the RTR-reply frame configured via RX_SETUP with +RX_RTR_FRAME. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++---------------- + 1 file changed, 41 insertions(+), 18 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index e8fb31b24bb2ae..2c12a5441eac48 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1241,22 +1241,37 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +-static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, +- struct bcm_op *op, void *new_frames) ++static int bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) + { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if (!(msg_head->flags & RX_RTR_FRAME)) ++ return 0; ++ ++ /* this frame is sent out as-is by bcm_can_tx() whenever a matching ++ * remote request is received, so validate its length the same way ++ * bcm_tx_setup() validates TX_SETUP frames before installing it ++ */ ++ if (msg_head->flags & CAN_FD_FRAME) { ++ if (frame0->len > 64) ++ return -EINVAL; ++ } else { ++ if (frame0->len > 8) ++ return -EINVAL; ++ } ++ + /* funny feature in RX(!)_SETUP only for RTR-mode: + * copy can_id into frame BUT without RTR-flag to + * prevent a full-load-loopback-test ... ;-] + * normalize this on the staged buffer, before it is + * ever installed into op->frames. + */ +- if (msg_head->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = new_frames; ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; + +- if ((msg_head->flags & TX_CP_CAN_ID) || +- frame0->can_id == op->can_id) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- } ++ return 0; + } + + /* +@@ -1319,7 +1334,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +- bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + } + + spin_lock_bh(&op->bcm_rx_update_lock); +@@ -1392,16 +1411,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes) { + err = memcpy_from_msg(op->frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) { +- if (op->frames != &op->sframe) +- kfree(op->frames); +- if (op->last_frames != &op->last_sframe) +- kfree(op->last_frames); +- kfree(op); +- return err; +- } ++ if (err < 0) ++ goto free_op; + +- bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ if (err < 0) ++ goto free_op; + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1503,6 +1518,14 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + } + + return msg_head->nframes * op->cfsiz + MHSIZ; ++ ++free_op: ++ if (op->frames != &op->sframe) ++ kfree(op->frames); ++ if (op->last_frames != &op->last_sframe) ++ kfree(op->last_frames); ++ kfree(op); ++ return err; + } + + /* +-- +2.53.0 + diff --git a/queue-6.12/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch b/queue-6.12/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch new file mode 100644 index 0000000000..54cc3eef82 --- /dev/null +++ b/queue-6.12/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch @@ -0,0 +1,238 @@ +From 2eee79484627c3537f88eaee2fcc10e764a6138d Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:34:54 +0200 +Subject: can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER + +From: Oliver Hartkopp + +commit 20bab8b88baac140ca3701116e1d486c7f51e311 upstream. + +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|rx].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 +Closes: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-2-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 88 +++++++++++++++++++++++++++++++++---------------- + 1 file changed, 59 insertions(+), 29 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index 176e16b46413fa..76e84e2113eeab 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -151,6 +151,7 @@ struct isotp_sock { + struct sock sk; + int bound; + int ifindex; ++ struct net_device *dev; + canid_t txid; + canid_t rxid; + ktime_t tx_gap; +@@ -964,6 +965,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + 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); +@@ -1199,28 +1208,30 @@ static int isotp_release(struct socket *sock) + 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); ++ dev_put(so->dev); + } + ++ 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 +@@ -1233,9 +1244,6 @@ static int isotp_release(struct socket *sock) + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->rxtimer); + +- so->ifindex = 0; +- so->bound = 0; +- + sock_orphan(sk); + sock->sk = NULL; + +@@ -1289,6 +1297,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + if (!addr->can_ifindex) + return -ENODEV; + ++ rtnl_lock(); + lock_sock(sk); + + if (so->bound) { +@@ -1296,6 +1305,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + ++ /* A transmission or reception that outlived a previous binding ++ * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo ++ * and RX 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 || so->rx.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; +@@ -1308,14 +1328,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + if (dev->type != ARPHRD_CAN) { +- dev_put(dev); + err = -ENODEV; +- goto out; ++ goto out_put_dev; + } +- if (dev->mtu < so->ll.mtu) { +- dev_put(dev); ++ if (READ_ONCE(dev->mtu) < so->ll.mtu) { + err = -EINVAL; +- goto out; ++ goto out_put_dev; + } + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; +@@ -1333,16 +1351,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + 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; ++ dev_hold(so->dev); ++ ++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; +@@ -1545,7 +1572,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + if (!net_eq(dev_net(dev), sock_net(sk))) + return; + +- if (so->ifindex != dev->ifindex) ++ if (so->dev != dev) + return; + + switch (msg) { +@@ -1561,10 +1588,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + can_rx_unregister(dev_net(dev), dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); ++ dev_put(so->dev); + } + + so->ifindex = 0; + so->bound = 0; ++ so->dev = NULL; + release_sock(sk); + + sk->sk_err = ENODEV; +@@ -1624,6 +1653,7 @@ static int isotp_init(struct sock *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; +-- +2.53.0 + diff --git a/queue-6.12/can-isotp-serialize-tx-state-transitions-under-so-rx.patch b/queue-6.12/can-isotp-serialize-tx-state-transitions-under-so-rx.patch new file mode 100644 index 0000000000..876bd91541 --- /dev/null +++ b/queue-6.12/can-isotp-serialize-tx-state-transitions-under-so-rx.patch @@ -0,0 +1,426 @@ +From 7f1b6867d956ef0f0afb0e024265a5ae4cdfb9cc Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:34:55 +0200 +Subject: can: isotp: serialize TX state transitions under so->rx_lock + +From: Oliver Hartkopp + +commit cf070fe33bfbd1a4c21236078fadb35dd223a157 upstream. + +The TX state machine (so->tx.state) is driven from three contexts: +sendmsg() claiming and progressing a transfer, the RX path consuming +Flow Control/echo frames, and two hrtimers timing out a stalled +transfer. Mixing a lock-free cmpxchg() claim in sendmsg() with +hrtimer_cancel() calls made under so->rx_lock elsewhere left windows +where a frame or timer callback could act on a state that had already +moved on, corrupting an unrelated transfer. + +so->rx_lock now covers the full lifecycle of a TX claim: sendmsg() +takes it to check so->tx.state is ISOTP_IDLE, switch it to +ISOTP_SENDING, bump so->tx_gen and drain the previous transfer's +timers - all as one critical section. isotp_rcv_fc()/isotp_rcv_cf() +already run under this lock via isotp_rcv(), and isotp_rcv_echo() now +takes it itself, so none of them can ever observe a transfer mid-claim. +This also means a transfer can no longer be handed to sendmsg()'s +cleanup paths (signal or send error) while another thread is +concurrently claiming or finishing it, so those paths can cancel +timers and reset the state unconditionally. + +isotp_release() claims the socket the same way, so a racing sendmsg() +sees a consistent ISOTP_SHUTDOWN and skips arming its timer or sending. + +Only the hrtimer callbacks stay outside so->rx_lock, since they run +under so->rx_lock's cancellation elsewhere and taking it themselves +would deadlock. so->tx_gen lets them recognize whether the transfer +they timed out is still the one currently active, so they don't +report an error against a transfer that has since completed or been +superseded. + +Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-3-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 192 ++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 154 insertions(+), 38 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index 76e84e2113eeab..b2622c881aa32b 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -156,7 +156,7 @@ struct isotp_sock { + canid_t rxid; + ktime_t tx_gap; + ktime_t lastrxcf_tstamp; +- struct hrtimer rxtimer, txtimer, txfrtimer; ++ struct hrtimer rxtimer, txtimer, txfrtimer, echotimer; + struct can_isotp_options opt; + struct can_isotp_fc_options rxfc, txfc; + struct can_isotp_ll_options ll; +@@ -164,6 +164,7 @@ struct isotp_sock { + u32 force_tx_stmin; + u32 force_rx_stmin; + u32 cfecho; /* consecutive frame echo tag */ ++ u32 tx_gen; /* generation, bumped per new tx transfer */ + struct tpcon rx, tx; + struct list_head notifier; + wait_queue_head_t wait; +@@ -370,6 +371,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + + hrtimer_cancel(&so->txtimer); + ++ /* isotp_tx_timeout() may have given up on this job while ++ * hrtimer_cancel() above waited for it to finish; so->rx_lock ++ * (held by our caller isotp_rcv()) rules out a concurrent claim, ++ * so a plain recheck is enough here. ++ */ ++ if (so->tx.state != ISOTP_WAIT_FC && ++ so->tx.state != ISOTP_WAIT_FIRST_FC) ++ return 1; ++ + if ((cf->len < ae + FC_CONTENT_SZ) || + ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { +@@ -416,7 +426,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + so->tx.bs = 0; + so->tx.state = ISOTP_SENDING; + /* send CF frame and enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); + break; +@@ -569,6 +579,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; ++ + /* CFs are never longer than the FF */ + if (cf->len > so->rx.ll_dl) + return 1; +@@ -858,20 +876,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + + /* only handle my own local echo CF/SF skb's (no FF!) */ +- if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) ++ if (skb->sk != sk) + return; + ++ /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock ++ * (no isotp_rcv() caller here), so take it ourselves ++ */ ++ spin_lock(&so->rx_lock); ++ ++ /* so->cfecho may since belong to a new transfer; recheck under lock */ ++ if (so->cfecho != *(u32 *)cf->data) ++ goto out_unlock; ++ + /* cancel local echo timeout */ +- hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + + /* local echo skb with consecutive frame has been consumed */ + so->cfecho = 0; + ++ /* claiming a transfer also takes so->rx_lock, so a plain recheck ++ * is enough: so->tx.state can't have flipped to ISOTP_SENDING for ++ * a new claim while we're still in here ++ */ ++ if (so->tx.state != ISOTP_SENDING) ++ goto out_unlock; ++ + if (so->tx.idx >= so->tx.len) { + /* we are done */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); +- return; ++ goto out_unlock; + } + + if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { +@@ -879,53 +913,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + so->tx.state = ISOTP_WAIT_FC; + hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); +- return; ++ goto out_unlock; + } + + /* no gap between data frames needed => use burst mode */ + if (!so->tx_gap) { + /* enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); +- return; ++ goto out_unlock; + } + + /* start timer to send next consecutive frame with correct delay */ + hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT); ++ ++out_unlock: ++ spin_unlock(&so->rx_lock); + } + +-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get ++ * cancelled under so->rx_lock elsewhere, so this must stay lock-free to ++ * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting ++ * a new transfer with an error from the one that just timed out. ++ */ ++static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) + { +- struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, +- txtimer); + struct sock *sk = &so->sk; ++ u32 gen = READ_ONCE(so->tx_gen); ++ u32 old_state = READ_ONCE(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 the timeout if the state is still unchanged */ ++ 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); ++ if (READ_ONCE(so->tx_gen) == gen) { ++ /* 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; + } + ++/* so->txtimer: fires when a Flow Control frame does not arrive in time */ ++static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ txtimer); ++ ++ return isotp_tx_timeout(so); ++} ++ ++/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */ ++static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ echotimer); ++ ++ return isotp_tx_timeout(so); ++} ++ + static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) + { + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txfrtimer); + + /* start echo timeout handling and cover below protocol error */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + + /* cfecho should be consumed by isotp_rcv_echo() here */ +@@ -945,13 +1009,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; + s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; ++ struct hrtimer *tx_hrt = &so->echotimer; ++ u32 new_state = ISOTP_SENDING; + int off; + int err; + + if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) + return -EADDRNOTAVAIL; + +- while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) { ++ /* claim the socket under so->rx_lock: this serializes the claim ++ * with the RX path and with sendmsg()'s own error paths below, so ++ * none of them can ever see a transfer mid-claim ++ */ ++ for (;;) { ++ spin_lock_bh(&so->rx_lock); ++ if (READ_ONCE(so->tx.state) == ISOTP_IDLE) ++ break; ++ spin_unlock_bh(&so->rx_lock); ++ + /* we do not support multiple buffers - for now */ + if (msg->msg_flags & MSG_DONTWAIT) + return -EAGAIN; +@@ -960,11 +1035,23 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + return -EADDRNOTAVAIL; + + /* wait for complete transmission of current pdu */ +- err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); ++ err = wait_event_interruptible(so->wait, ++ so->tx.state == ISOTP_IDLE); + if (err) +- goto err_event_drop; ++ return err; + } + ++ /* new transfer: bump so->tx_gen and drain the old one's timers, ++ * still under the so->rx_lock we just claimed the socket with ++ */ ++ WRITE_ONCE(so->tx.state, ISOTP_SENDING); ++ WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); ++ hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); ++ hrtimer_cancel(&so->txfrtimer); ++ so->cfecho = 0; ++ spin_unlock_bh(&so->rx_lock); ++ + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ +@@ -1075,18 +1162,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + so->cfecho = *(u32 *)cf->data; + } else { + /* standard flow control check */ +- so->tx.state = ISOTP_WAIT_FIRST_FC; ++ new_state = ISOTP_WAIT_FIRST_FC; + + /* start timeout for FC */ + hrtimer_sec = ISOTP_FC_TIMEOUT; ++ tx_hrt = &so->txtimer; + + /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ + so->cfecho = 0; + } + } + +- hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0), ++ spin_lock_bh(&so->rx_lock); ++ if (so->tx.state == ISOTP_SHUTDOWN) { ++ /* isotp_release() has since taken over and already drained ++ * our timers - don't send into a socket that's going away ++ */ ++ spin_unlock_bh(&so->rx_lock); ++ kfree_skb(skb); ++ dev_put(dev); ++ wake_up_interruptible(&so->wait); ++ return -EADDRNOTAVAIL; ++ } ++ /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ ++ so->tx.state = new_state; ++ hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), + HRTIMER_MODE_REL_SOFT); ++ spin_unlock_bh(&so->rx_lock); + + /* send the first or only CAN frame */ + cf->flags = so->ll.tx_flags; +@@ -1099,13 +1201,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + pr_notice_once("can-isotp: %s: can_send_ret %pe\n", + __func__, ERR_PTR(err)); + ++ spin_lock_bh(&so->rx_lock); + /* no transmission -> no timeout monitoring */ +- hrtimer_cancel(&so->txtimer); +- +- /* reset consecutive frame echo tag */ +- so->cfecho = 0; +- +- goto err_out_drop; ++ hrtimer_cancel(tx_hrt); ++ goto err_out_drop_locked; + } + + if (wait_tx_done) { +@@ -1121,14 +1220,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + + return size; + ++err_out_drop: ++ /* claimed but nothing sent yet - no timer to cancel */ ++ spin_lock_bh(&so->rx_lock); ++ goto err_out_drop_locked; + err_event_drop: +- /* got signal: force tx state machine to be idle */ +- so->tx.state = ISOTP_IDLE; ++ /* interrupted waiting on our own transfer - drain its timers */ ++ spin_lock_bh(&so->rx_lock); + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); +-err_out_drop: +- /* drop this PDU and unlock a potential wait queue */ ++ hrtimer_cancel(&so->echotimer); ++err_out_drop_locked: ++ /* release the claim; so->rx_lock still held from above */ ++ so->cfecho = 0; + so->tx.state = ISOTP_IDLE; ++ spin_unlock_bh(&so->rx_lock); + wake_up_interruptible(&so->wait); + + return err; +@@ -1190,13 +1296,20 @@ static int isotp_release(struct socket *sock) + so = isotp_sk(sk); + net = sock_net(sk); + +- /* wait for complete transmission of current pdu */ +- while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 && +- cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE) ++ /* best-effort: wait for a running pdu to finish, but don't block on ++ * it forever - give up after the first signal ++ */ ++ while (so->tx.state != ISOTP_IDLE && ++ wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) + ; + +- /* force state machines to be idle also when a signal occurred */ ++ /* claim the socket under so->rx_lock like sendmsg() does, so its ++ * claim can't race the forced ISOTP_SHUTDOWN below; force it ++ * unconditionally, even when a signal cut the wait above short ++ */ ++ spin_lock_bh(&so->rx_lock); + so->tx.state = ISOTP_SHUTDOWN; ++ spin_unlock_bh(&so->rx_lock); + so->rx.state = ISOTP_IDLE; + + spin_lock(&isotp_notifier_lock); +@@ -1242,6 +1355,7 @@ static int isotp_release(struct socket *sock) + + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + hrtimer_cancel(&so->rxtimer); + + sock_orphan(sk); +@@ -1684,6 +1798,8 @@ static int isotp_init(struct sock *sk) + so->rxtimer.function = isotp_rx_timer_handler; + hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txtimer.function = isotp_tx_timer_handler; ++ hrtimer_init(&so->echotimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ so->echotimer.function = isotp_echo_timer_handler; + hrtimer_init(&so->txfrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txfrtimer.function = isotp_txfr_timer_handler; + +-- +2.53.0 + diff --git a/queue-6.12/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch b/queue-6.12/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch new file mode 100644 index 0000000000..6cef366629 --- /dev/null +++ b/queue-6.12/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch @@ -0,0 +1,44 @@ +From e33be45369ff2478ba7dfa8afae516c14284e40a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 15 May 2026 11:34:52 +0900 +Subject: crypto: tegra - Don't touch bo refcount in host1x bo pin/unpin + +From: Mikko Perttunen + +[ Upstream commit f8c9c57d750346abd213ffed2ae3cacb0268e9f1 ] + +Since commit "gpu: host1x: Allow entries in BO caches to be freed", +host1x_bo_pin() and host1x_bo_unpin() handle the bo's refcount +themselves. .pin/.unpin callbacks should not adjust it. + +Signed-off-by: Mikko Perttunen +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +--- + drivers/crypto/tegra/tegra-se-main.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/drivers/crypto/tegra/tegra-se-main.c b/drivers/crypto/tegra/tegra-se-main.c +index 3c3b9f18d8cf50..e5ff4fe66a4b88 100644 +--- a/drivers/crypto/tegra/tegra-se-main.c ++++ b/drivers/crypto/tegra/tegra-se-main.c +@@ -52,7 +52,7 @@ tegra_se_cmdbuf_pin(struct device *dev, struct host1x_bo *bo, enum dma_data_dire + return ERR_PTR(-ENOMEM); + + kref_init(&map->ref); +- map->bo = host1x_bo_get(bo); ++ map->bo = bo; + map->direction = direction; + map->dev = dev; + +@@ -93,7 +93,6 @@ static void tegra_se_cmdbuf_unpin(struct host1x_bo_mapping *map) + dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0); + sg_free_table(map->sgt); + kfree(map->sgt); +- host1x_bo_put(map->bo); + + kfree(map); + } +-- +2.53.0 + diff --git a/queue-6.12/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch b/queue-6.12/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch new file mode 100644 index 0000000000..af020ec9b0 --- /dev/null +++ b/queue-6.12/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch @@ -0,0 +1,204 @@ +From 267efe19ee69ee5e21c927ea2daef6bdf4a2a10f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 15:00:06 +0200 +Subject: dmaengine: sh: rz-dmac: Move interrupt request after everything is + set up + +From: Claudiu Beznea + +commit 731712403ddb39d1a76a11abf339a0615bc85de7 upstream. + +Once the interrupt is requested, the interrupt handler may run immediately. +Since the IRQ handler can access channel->ch_base, which is initialized +only after requesting the IRQ, this may lead to invalid memory access. +Likewise, the IRQ thread may access uninitialized data (the ld_free, +ld_queue, and ld_active lists), which may also lead to issues. + +Request the interrupts only after everything is set up. To keep the error +path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent(). + +Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC") +Cc: stable@vger.kernel.org +Reviewed-by: Frank Li +Tested-by: John Madieu +Signed-off-by: Claudiu Beznea +Tested-by: Tommaso Merciai +Link: https://patch.msgid.link/20260526084710.3491480-2-claudiu.beznea@kernel.org +[tm: Kept the channel->irq field in rz_dmac_chan_probe() instead of + upstream's local `irq` variable, as commit 04e227718ab8 + ("dmaengine: sh: rz-dmac: Make channel irq local") is not present + in this tree. Likewise kept platform_get_irq_byname() (mandatory) + instead of platform_get_irq_byname_optional() for the error IRQ in + rz_dmac_probe(), as commit 6b3a6b6dc074 ("dmaengine: sh: rz_dmac: + make error interrupt optional") is not present in this tree either; + its early return on failure becomes a goto err jump to match the + new call order.] +Signed-off-by: Vinod Koul +Signed-off-by: Tommaso Merciai +Signed-off-by: Sasha Levin +--- + drivers/dma/sh/rz-dmac.c | 96 ++++++++++++++++------------------------ + 1 file changed, 38 insertions(+), 58 deletions(-) + +diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c +index 06952519f509b8..fece2cfec1ccb4 100644 +--- a/drivers/dma/sh/rz-dmac.c ++++ b/drivers/dma/sh/rz-dmac.c +@@ -780,27 +780,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + channel->index = index; + channel->mid_rid = -EINVAL; + +- /* Request the channel interrupt. */ +- scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index); +- channel->irq = platform_get_irq_byname(pdev, pdev_irqname); +- if (channel->irq < 0) +- return channel->irq; +- +- irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", +- dev_name(dmac->dev), index); +- if (!irqname) +- return -ENOMEM; +- +- ret = devm_request_threaded_irq(dmac->dev, channel->irq, +- rz_dmac_irq_handler, +- rz_dmac_irq_handler_thread, 0, +- irqname, channel); +- if (ret) { +- dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", +- channel->irq, ret); +- return ret; +- } +- + /* Set io base address for each channel */ + if (index < 8) { + channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET + +@@ -813,9 +792,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + } + + /* Allocate descriptors */ +- lmdesc = dma_alloc_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- &channel->lmdesc.base_dma, GFP_KERNEL); ++ lmdesc = dmam_alloc_coherent(&pdev->dev, ++ sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, ++ &channel->lmdesc.base_dma, GFP_KERNEL); + if (!lmdesc) { + dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n"); + return -ENOMEM; +@@ -831,7 +810,26 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + INIT_LIST_HEAD(&channel->ld_free); + INIT_LIST_HEAD(&channel->ld_active); + +- return 0; ++ /* Request the channel interrupt. */ ++ scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index); ++ channel->irq = platform_get_irq_byname(pdev, pdev_irqname); ++ if (channel->irq < 0) ++ return channel->irq; ++ ++ irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", ++ dev_name(dmac->dev), index); ++ if (!irqname) ++ return -ENOMEM; ++ ++ ret = devm_request_threaded_irq(dmac->dev, channel->irq, ++ rz_dmac_irq_handler, ++ rz_dmac_irq_handler_thread, 0, ++ irqname, channel); ++ if (ret) ++ dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", ++ channel->irq, ret); ++ ++ return ret; + } + + static int rz_dmac_parse_of(struct device *dev, struct rz_dmac *dmac) +@@ -858,7 +856,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + const char *irqname = "error"; + struct dma_device *engine; + struct rz_dmac *dmac; +- int channel_num; + int ret; + int irq; + u8 i; +@@ -888,19 +885,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + if (IS_ERR(dmac->ext_base)) + return PTR_ERR(dmac->ext_base); + +- /* Register interrupt handler for error */ +- irq = platform_get_irq_byname(pdev, irqname); +- if (irq < 0) +- return irq; +- +- ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, +- irqname, NULL); +- if (ret) { +- dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", +- irq, ret); +- return ret; +- } +- + /* Initialize the channels. */ + INIT_LIST_HEAD(&dmac->engine.channels); + +@@ -926,6 +910,21 @@ static int rz_dmac_probe(struct platform_device *pdev) + goto err; + } + ++ /* Register interrupt handler for error */ ++ irq = platform_get_irq_byname(pdev, irqname); ++ if (irq < 0) { ++ ret = irq; ++ goto err; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, ++ irqname, NULL); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", ++ irq, ret); ++ goto err; ++ } ++ + /* Register the DMAC as a DMA provider for DT. */ + ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate, + NULL); +@@ -964,16 +963,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + dma_register_err: + of_dma_controller_free(pdev->dev.of_node); + err: +- channel_num = i ? i - 1 : 0; +- for (i = 0; i < channel_num; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } +- + reset_control_assert(dmac->rstc); + err_pm_runtime_put: + pm_runtime_put(&pdev->dev); +@@ -986,18 +975,9 @@ static int rz_dmac_probe(struct platform_device *pdev) + static void rz_dmac_remove(struct platform_device *pdev) + { + struct rz_dmac *dmac = platform_get_drvdata(pdev); +- unsigned int i; + + dma_async_device_unregister(&dmac->engine); + of_dma_controller_free(pdev->dev.of_node); +- for (i = 0; i < dmac->n_channels; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } + reset_control_assert(dmac->rstc); + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); +-- +2.53.0 + diff --git a/queue-6.12/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch b/queue-6.12/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch new file mode 100644 index 0000000000..02f63ae334 --- /dev/null +++ b/queue-6.12/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch @@ -0,0 +1,43 @@ +From 377e89f39a5ab0706619c9f117c02c14bdd170a2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 3 Jun 2026 17:37:49 +0900 +Subject: gpu: host1x: Fix use-after-free in host1x_bo_clear_cached_mappings + +From: Mikko Perttunen + +[ Upstream commit 266cddf7bd0f6c79b6c0633aef742a22bf70265b ] + +__host1x_bo_unpin() drops the last reference to the mapping and frees +it, so we can't dereference mapping afterwards. The cache itself +outlives the mapping, so use the cache local variable instead. + +Reported-by: Dan Carpenter +Closes: https://lore.kernel.org/linux-tegra/ah6ErK6f4kVudVIA@stanley.mountain/T/#u +Signed-off-by: Mikko Perttunen +Signed-off-by: Thierry Reding +Link: https://patch.msgid.link/20260603-host1x-bocache-leak-fix-v1-1-494101dbfd30@nvidia.com +Signed-off-by: Sasha Levin +--- + drivers/gpu/host1x/bus.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c +index ef42e9a883f4f9..4dd83627fae06a 100644 +--- a/drivers/gpu/host1x/bus.c ++++ b/drivers/gpu/host1x/bus.c +@@ -1006,10 +1006,10 @@ void host1x_bo_clear_cached_mappings(struct host1x_bo *bo) + if (WARN_ON(!cache)) + continue; + +- mutex_lock(&mapping->cache->lock); ++ mutex_lock(&cache->lock); + WARN_ON(kref_read(&mapping->ref) != 1); + __host1x_bo_unpin(&mapping->ref); +- mutex_unlock(&mapping->cache->lock); ++ mutex_unlock(&cache->lock); + } + } + EXPORT_SYMBOL(host1x_bo_clear_cached_mappings); +-- +2.53.0 + diff --git a/queue-6.12/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch b/queue-6.12/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch new file mode 100644 index 0000000000..09a4743edb --- /dev/null +++ b/queue-6.12/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch @@ -0,0 +1,114 @@ +From 92bbe6326706daf9ab63a168cb85f421ae4b223d Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 8 Apr 2026 09:03:59 -0700 +Subject: Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data() + +From: Seungjin Bae + +[ Upstream commit 875115b82c295277b81b6dfee7debc725f44e854 ] + +The `ims_pcu_process_data()` processes incoming URB data byte by byte. +However, it fails to check if the `read_pos` index exceeds +IMS_PCU_BUF_SIZE. + +If a malicious USB device sends a packet larger than IMS_PCU_BUF_SIZE, +`read_pos` will increment indefinitely. Moreover, since `read_pos` is +located immediately after `read_buf`, the attacker can overwrite +`read_pos` itself to arbitrarily control the index. + +This manipulated `read_pos` is subsequently used in +`ims_pcu_handle_response()` to copy data into `cmd_buf`, leading to a +heap buffer overflow. + +Specifically, an attacker can overwrite the `cmd_done.wait.head` located +at offset 136 relative to `cmd_buf` in the `ims_pcu_handle_response()`. +Consequently, when the driver calls `complete(&pcu->cmd_done)`, it +triggers a control flow hijack by using the manipulated pointer. + +Fix this by adding a bounds check for `read_pos` before writing to +`read_buf`. If the packet is too long, discard it, log a warning, +and reset the parser state. + +Fixes: 628329d524743 ("Input: add IMS Passenger Control Unit driver") +Co-developed-by: Sanghoon Choi +Signed-off-by: Sanghoon Choi +Signed-off-by: Seungjin Bae +Link: https://patch.msgid.link/20251221211442.841549-2-eeodqql09@gmail.com +[dtor: factor out resetting packet state, reset checksum as well] +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 32 ++++++++++++++++++++++++++------ + 1 file changed, 26 insertions(+), 6 deletions(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 4073e66e4d629e..3dc624c75bb5c5 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -448,6 +448,14 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + } + } + ++static void ims_pcu_reset_packet(struct ims_pcu *pcu) ++{ ++ pcu->have_stx = true; ++ pcu->have_dle = false; ++ pcu->read_pos = 0; ++ pcu->check_sum = 0; ++} ++ + static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + { + int i; +@@ -460,6 +468,14 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + continue; + + if (pcu->have_dle) { ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->have_dle = false; + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; +@@ -472,10 +488,8 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + dev_warn(pcu->dev, + "Unexpected STX at byte %d, discarding old data\n", + pcu->read_pos); ++ ims_pcu_reset_packet(pcu); + pcu->have_stx = true; +- pcu->have_dle = false; +- pcu->read_pos = 0; +- pcu->check_sum = 0; + break; + + case IMS_PCU_PROTOCOL_DLE: +@@ -495,12 +509,18 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + ims_pcu_handle_response(pcu); + } + +- pcu->have_stx = false; +- pcu->have_dle = false; +- pcu->read_pos = 0; ++ ims_pcu_reset_packet(pcu); + break; + + default: ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + break; +-- +2.53.0 + diff --git a/queue-6.12/input-ims-pcu-fix-logic-error-in-packet-reset.patch b/queue-6.12/input-ims-pcu-fix-logic-error-in-packet-reset.patch new file mode 100644 index 0000000000..47a1a2a668 --- /dev/null +++ b/queue-6.12/input-ims-pcu-fix-logic-error-in-packet-reset.patch @@ -0,0 +1,42 @@ +From 3aadebb970adeb16b0bb9651a6c88578b5124ab1 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 22 May 2026 10:29:41 -0700 +Subject: Input: ims-pcu - fix logic error in packet reset + +From: Dmitry Torokhov + +[ Upstream commit 2c9b85a14abb4811e8d4773ccd13559e59792efb ] + +ims_pcu_reset_packet() incorrectly sets have_stx to true, which implies +that the start-of-packet delimiter has already been received. This +causes the protocol parser to skip waiting for the next STX byte and +potentially process garbage data. + +Correctly set have_stx to false when resetting the packet state. + +Fixes: 875115b82c29 ("Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data()") +Cc: stable@vger.kernel.org +Reported-by: Sashiko bot +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 3dc624c75bb5c5..4cfcf6e945bba5 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -450,7 +450,7 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + + static void ims_pcu_reset_packet(struct ims_pcu *pcu) + { +- pcu->have_stx = true; ++ pcu->have_stx = false; + pcu->have_dle = false; + pcu->read_pos = 0; + pcu->check_sum = 0; +-- +2.53.0 + diff --git a/queue-6.12/revert-arm64-dts-ti-k3-am62a7-sk-add-bootph-all-tag-.patch b/queue-6.12/revert-arm64-dts-ti-k3-am62a7-sk-add-bootph-all-tag-.patch new file mode 100644 index 0000000000..4c9dfc743b --- /dev/null +++ b/queue-6.12/revert-arm64-dts-ti-k3-am62a7-sk-add-bootph-all-tag-.patch @@ -0,0 +1,35 @@ +From 211d2798eb29ec6d49b876e73ffb37ab5a082900 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 21:49:33 -0400 +Subject: Revert "arm64: dts: ti: k3-am62a7-sk: Add bootph-all tag to vqmmc" + +This reverts commit 1b21939117aea112f6b7fa67bba6fb43d2a8e8ec. + +Signed-off-by: Sasha Levin +--- + arch/arm64/boot/dts/ti/k3-am62a7-sk.dts | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/ti/k3-am62a7-sk.dts b/arch/arm64/boot/dts/ti/k3-am62a7-sk.dts +index f94378379447a6..0b6e371eea98d2 100644 +--- a/arch/arm64/boot/dts/ti/k3-am62a7-sk.dts ++++ b/arch/arm64/boot/dts/ti/k3-am62a7-sk.dts +@@ -134,7 +134,6 @@ vddshv_sdio: regulator-5 { + gpios = <&main_gpio0 31 GPIO_ACTIVE_HIGH>; + states = <1800000 0x0>, + <3300000 0x1>; +- bootph-all; + }; + + leds { +@@ -382,7 +381,6 @@ pmic_irq_pins_default: pmic-irq-default-pins { + pinctrl-single,pins = < + AM62AX_MCU_IOPAD(0x000, PIN_INPUT, 7) /* (E11) MCU_GPIO0_0 */ + >; +- bootph-all; + }; + }; + +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index c22ed8bec4..f8f3aa0e24 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -15,3 +15,21 @@ kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch kvm-x86-mmu-fix-use-after-free-on-vendor-module-reload.patch +can-bcm-add-locking-when-updating-filter-and-timer-v.patch +can-bcm-fix-can-frame-rx-tx-statistics.patch +can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch +can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch +can-bcm-add-missing-device-refcount-for-can-filter-r.patch +can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch +can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch +can-bcm-track-a-single-source-interface-for-anydev-t.patch +can-isotp-fix-use-after-free-race-with-concurrent-ne.patch +can-isotp-serialize-tx-state-transitions-under-so-rx.patch +dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch +revert-arm64-dts-ti-k3-am62a7-sk-add-bootph-all-tag-.patch +gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch +crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch +xprtrdma-clear-receive-side-ownership-pointers-on-re.patch +input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch +input-ims-pcu-fix-logic-error-in-packet-reset.patch +soc-qcom-ice-allow-explicit-votes-on-iface-clock-for.patch diff --git a/queue-6.12/soc-qcom-ice-allow-explicit-votes-on-iface-clock-for.patch b/queue-6.12/soc-qcom-ice-allow-explicit-votes-on-iface-clock-for.patch new file mode 100644 index 0000000000..482b5a0eac --- /dev/null +++ b/queue-6.12/soc-qcom-ice-allow-explicit-votes-on-iface-clock-for.patch @@ -0,0 +1,87 @@ +From 9e64c6d584529e2672d1cfc5b8ec7a43714865c0 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 16 Apr 2026 17:29:19 +0530 +Subject: soc: qcom: ice: Allow explicit votes on 'iface' clock for ICE + +From: Harshal Dev + +[ Upstream commit 0d5dc5818191b55e4364d04b1b898a14a2ccac38 ] + +Since Qualcomm inline-crypto engine (ICE) is now a dedicated driver +de-coupled from the QCOM UFS driver, it explicitly votes for its required +clocks during probe. For scenarios where the 'clk_ignore_unused' flag is +not passed on the kernel command line, to avoid potential unclocked ICE +hardware register access during probe the ICE driver should additionally +vote on the 'iface' clock. +Also update the suspend and resume callbacks to handle un-voting and voting +on the 'iface' clock. + +Fixes: 2afbf43a4aec6 ("soc: qcom: Make the Qualcomm UFS/SDCC ICE a dedicated driver") +Reviewed-by: Manivannan Sadhasivam +Reviewed-by: Kuldeep Singh +Reviewed-by: Konrad Dybcio +Signed-off-by: Harshal Dev +Link: https://lore.kernel.org/r/20260416-qcom_ice_power_and_clk_vote-v5-2-5ccf5d7e2846@oss.qualcomm.com +Signed-off-by: Bjorn Andersson +Signed-off-by: Sasha Levin +--- + drivers/soc/qcom/ice.c | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/drivers/soc/qcom/ice.c b/drivers/soc/qcom/ice.c +index 85504a023e326a..0aa83175fbda10 100644 +--- a/drivers/soc/qcom/ice.c ++++ b/drivers/soc/qcom/ice.c +@@ -49,6 +49,7 @@ struct qcom_ice { + struct device_link *link; + + struct clk *core_clk; ++ struct clk *iface_clk; + }; + + static DEFINE_XARRAY(ice_handles); +@@ -150,8 +151,13 @@ int qcom_ice_resume(struct qcom_ice *ice) + + err = clk_prepare_enable(ice->core_clk); + if (err) { +- dev_err(dev, "failed to enable core clock (%d)\n", +- err); ++ dev_err(dev, "Failed to enable core clock: %d\n", err); ++ return err; ++ } ++ ++ err = clk_prepare_enable(ice->iface_clk); ++ if (err) { ++ dev_err(dev, "Failed to enable iface clock: %d\n", err); + return err; + } + +@@ -161,6 +167,7 @@ EXPORT_SYMBOL_GPL(qcom_ice_resume); + + int qcom_ice_suspend(struct qcom_ice *ice) + { ++ clk_disable_unprepare(ice->iface_clk); + clk_disable_unprepare(ice->core_clk); + + return 0; +@@ -241,11 +248,17 @@ static struct qcom_ice *qcom_ice_create(struct device *dev, + engine->core_clk = devm_clk_get_optional_enabled(dev, "ice_core_clk"); + if (!engine->core_clk) + engine->core_clk = devm_clk_get_optional_enabled(dev, "ice"); ++ if (!engine->core_clk) ++ engine->core_clk = devm_clk_get_optional_enabled(dev, "core"); + if (!engine->core_clk) + engine->core_clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(engine->core_clk)) + return ERR_CAST(engine->core_clk); + ++ engine->iface_clk = devm_clk_get_optional_enabled(dev, "iface"); ++ if (IS_ERR(engine->iface_clk)) ++ return ERR_CAST(engine->iface_clk); ++ + if (!qcom_ice_check_supported(engine)) + return ERR_PTR(-EOPNOTSUPP); + +-- +2.53.0 + diff --git a/queue-6.12/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch b/queue-6.12/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch new file mode 100644 index 0000000000..58aa5c54a6 --- /dev/null +++ b/queue-6.12/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch @@ -0,0 +1,110 @@ +From ade5088656cc856242934b93b6d56630edf81136 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 10:14:04 -0400 +Subject: xprtrdma: Clear receive-side ownership pointers on release + +From: Chuck Lever + +[ Upstream commit 2ae8e7afbc63bf84243367f89eb43571f0345a74 ] + +Three small ownership-state cleanups land the transport in a +state that lets future reviewers reason about each pointer +locally rather than tracing the whole reply path: + +rpcrdma_rep_put() clears rep->rr_rqst before the rep enters +rb_free_reps so that no rep on the free list still carries a +stale rqst pointer. rpcrdma_reply_handler() and +rpcrdma_unpin_rqst() are the only sites that set rr_rqst; +rpcrdma_reply_handler() hands the rep through +rpcrdma_rep_put(), and rpcrdma_unpin_rqst() NULLs rr_rqst +directly because its error path abandons the rep for +teardown cleanup rather than returning it to rb_free_reps. + +rpcrdma_reply_put() NULLs req->rl_reply before calling +rpcrdma_rep_put(). The previous order placed the rep on +rb_free_reps while req->rl_reply still pointed at it; the +window was harmless because xprt_rdma_free_slot() holds the +req exclusively across the pair, but closing it makes the +invariant 'rep on rb_free_reps implies no req references it' +strictly checkable. + +rpcrdma_sendctx_unmap() and rpcrdma_sendctx_cancel() clear +req->rl_sendctx after dropping the sendctx pointer in the +sendctx ring. Without this, req->rl_sendctx survives across +Send completion and points at a sendctx that may already have +been reassigned by rpcrdma_sendctx_get_locked() to a different +req. No caller dereferences the stale pointer today -- +rpcrdma_prepare_send_sges() overwrites it before the next +Send -- but a NULL is a more honest representation of 'the +Send is no longer outstanding' and lets the assertion patch +that follows trip on any future regression. + +Signed-off-by: Chuck Lever +Signed-off-by: Anna Schumaker +Signed-off-by: Sasha Levin +--- + net/sunrpc/xprtrdma/rpc_rdma.c | 4 ++++ + net/sunrpc/xprtrdma/verbs.c | 12 ++++++++++-- + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c +index e201b37578a70e..aa57e057ff451f 100644 +--- a/net/sunrpc/xprtrdma/rpc_rdma.c ++++ b/net/sunrpc/xprtrdma/rpc_rdma.c +@@ -542,6 +542,7 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + rpcrdma_req_put(req); + } + +@@ -550,8 +551,11 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + */ + static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) + { ++ struct rpcrdma_req *req = sc->sc_req; ++ + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + } + + /* Prepare an SGE for the RPC-over-RDMA transport header. +diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c +index efd91ed249fabe..e2d3cee825cd7f 100644 +--- a/net/sunrpc/xprtrdma/verbs.c ++++ b/net/sunrpc/xprtrdma/verbs.c +@@ -1080,9 +1080,15 @@ static struct rpcrdma_rep *rpcrdma_rep_get_locked(struct rpcrdma_buffer *buf) + * @buf: buffer pool + * @rep: rep to release + * ++ * The rep's transient association with an rpc_rqst, established ++ * by rpcrdma_reply_handler() and torn down here, must not survive ++ * onto rb_free_reps: rpcrdma_post_recvs() pulls reps from the free ++ * list to re-post them, and a non-NULL rr_rqst on a free-listed rep ++ * would imply the rep is still referenced by a req. + */ + void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep) + { ++ rep->rr_rqst = NULL; + llist_add(&rep->rr_node, &buf->rb_free_reps); + } + +@@ -1265,9 +1271,11 @@ rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt) + */ + void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) + { +- if (req->rl_reply) { +- rpcrdma_rep_put(buffers, req->rl_reply); ++ struct rpcrdma_rep *rep = req->rl_reply; ++ ++ if (rep) { + req->rl_reply = NULL; ++ rpcrdma_rep_put(buffers, rep); + } + /* I2: rl_reply NULL after the put closes the + * 'rep on rb_free_reps still referenced by req' window. +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-add-locking-when-updating-filter-and-timer-v.patch b/queue-6.18/can-bcm-add-locking-when-updating-filter-and-timer-v.patch new file mode 100644 index 0000000000..6a227c2815 --- /dev/null +++ b/queue-6.18/can-bcm-add-locking-when-updating-filter-and-timer-v.patch @@ -0,0 +1,417 @@ +From aaa7b99cc992f559597e50b241226447d5e5a5e1 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:51 +0200 +Subject: can: bcm: add locking when updating filter and timer values + +From: Oliver Hartkopp + +commit 749179c2e25b95d22499ed29096b3e02d6dfd2b4 upstream. + +KCSAN detected a simultaneous access to timer values that can be +overwritten in bcm_rx_setup() when updating timer and filter content +while bcm_rx_handler(), bcm_rx_timeout_handler() or bcm_rx_thr_handler() +run concurrently on incoming CAN traffic. + +Protect the timer (ival1/ival2/kt_ival1/kt_ival2/kt_lastmsg) and filter +(nframes/flags/frames/last_frames) updates in bcm_rx_setup() with a new +per-op bcm_rx_update_lock, taken with the matching scope in the RX +handlers. memcpy_from_msg() is staged into a temporary buffer before the +lock is taken, since it can sleep and must not run under a spinlock. + +hrtimer_cancel() is always called without bcm_rx_update_lock held, since +bcm_rx_timeout_handler()/bcm_rx_thr_handler() take the same lock and a +running callback would otherwise deadlock against the canceller. + +Also close a related race: bcm_rx_setup() cleared the RTR flag in the +stored reply frame's can_id as a separate, unprotected step after the +frame content was already installed, so a concurrent bcm_rx_handler() +could transmit a stale reply with CAN_RTR_FLAG still set. Fold that +normalization into the initial frame preparation instead (on the staged +buffer for updates, directly on op->frames pre-registration for new +ops), so the installed frame is always atomically self-consistent. + +bcm_rx_handler()'s RX_RTR_FRAME check now takes a lock-protected +snapshot of op->flags before deciding whether to call bcm_can_tx(), +but does not hold the lock across that call. + +Also take a lock-protected snapshot of the currframe in bcm_can_tx() +to avoid partly overwrites by content updates in bcm_tx_setup(). +Finally check if a TX_RESET_MULTI_IDX/SETTIMER might have reset +op->currframe between the two locked sections in bcm_can_tx(). + +Omit calling hrtimer_forward() with zero interval in bcm_rx_thr_handler(). +kt_ival2 may have been concurrently cleared by bcm_rx_setup() before it +cancels this timer, so check kt_ival2 inside the bcm_rx_update_lock. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Reported-by: syzbot+75e5e4ae00c3b4bb544e@syzkaller.appspotmail.com +Closes: https://lore.kernel.org/linux-can/6975d5cf.a00a0220.33ccc7.0022.GAE@google.com/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 176 ++++++++++++++++++++++++++++++++++++++------------ + 1 file changed, 133 insertions(+), 43 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index a40519638a262b..cfc495106aac4d 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -128,6 +128,7 @@ struct bcm_op { + struct sock *sk; + struct net_device *rx_reg_dev; + spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + + struct bcm_sock { +@@ -292,21 +293,27 @@ static int bcm_proc_show(struct seq_file *m, void *v) + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op + */ +-static void bcm_can_tx(struct bcm_op *op) ++static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + { + struct sk_buff *skb; + struct net_device *dev; +- struct canfd_frame *cf; ++ struct canfd_frame cframe; ++ bool cyclic = !cf; ++ unsigned int idx = 0; + int err; + + /* no target device? => exit */ + if (!op->ifindex) + return; + +- /* read currframe under lock protection */ +- spin_lock_bh(&op->bcm_tx_lock); +- cf = op->frames + op->cfsiz * op->currframe; +- spin_unlock_bh(&op->bcm_tx_lock); ++ if (cyclic) { ++ /* read currframe under lock protection */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ idx = op->currframe; ++ memcpy(&cframe, op->frames + op->cfsiz * idx, op->cfsiz); ++ cf = &cframe; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ } + + dev = dev_get_by_index(sock_net(op->sk), op->ifindex); + if (!dev) { +@@ -335,14 +342,20 @@ static void bcm_can_tx(struct bcm_op *op) + if (!err) + op->frames_abs++; + +- op->currframe++; ++ /* only advance the cyclic sequence if nothing reset currframe while ++ * we were sending - a concurrent TX_RESET_MULTI_IDX means this ++ * frame's bookkeeping belongs to a sequence that no longer exists ++ */ ++ if (!cyclic || op->currframe == idx) { ++ op->currframe++; + +- /* reached last frame? */ +- if (op->currframe >= op->nframes) +- op->currframe = 0; ++ /* reached last frame? */ ++ if (op->currframe >= op->nframes) ++ op->currframe = 0; + +- if (op->count > 0) +- op->count--; ++ if (op->count > 0) ++ op->count--; ++ } + + spin_unlock_bh(&op->bcm_tx_lock); + out: +@@ -456,7 +469,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_msg_head msg_head; + + if (op->kt_ival1 && (op->count > 0)) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + if (!op->count && (op->flags & TX_COUNTEVT)) { + + /* create notification to user */ +@@ -473,7 +486,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + } + + } else if (op->kt_ival2) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + } + + return bcm_tx_set_expiry(op, &op->timer) ? +@@ -617,6 +630,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + /* if user wants to be informed, when cyclic CAN-Messages come back */ + if ((op->flags & RX_ANNOUNCE_RESUME) && op->last_frames) { + /* clear received CAN frames to indicate 'nothing received' */ +@@ -633,6 +648,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ + bcm_send_to_user(op, &msg_head, NULL, 0); + + return HRTIMER_NORESTART; +@@ -681,15 +698,26 @@ static int bcm_rx_thr_flush(struct bcm_op *op) + static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer); ++ enum hrtimer_restart ret; + +- if (bcm_rx_thr_flush(op)) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ /* kt_ival2 may have been concurrently cleared by bcm_rx_setup() ++ * before it cancels this timer - never forward with a zero ++ * interval in that case. ++ */ ++ if (bcm_rx_thr_flush(op) && op->kt_ival2) { + hrtimer_forward_now(hrtimer, op->kt_ival2); +- return HRTIMER_RESTART; ++ ret = HRTIMER_RESTART; + } else { + /* rearm throttle handling */ + op->kt_lastmsg = 0; +- return HRTIMER_NORESTART; ++ ret = HRTIMER_NORESTART; + } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ return ret; + } + + /* +@@ -699,8 +727,10 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + { + struct bcm_op *op = (struct bcm_op *)data; + const struct canfd_frame *rxframe = (struct canfd_frame *)skb->data; ++ struct canfd_frame rtrframe; + unsigned int i; + unsigned char traffic_flags; ++ bool rtr_frame; + + if (op->can_id != rxframe->can_id) + return; +@@ -724,9 +754,18 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* update statistics */ + op->frames_abs++; + +- if (op->flags & RX_RTR_FRAME) { ++ /* snapshot the flag under lock: op->flags/op->frames may be updated ++ * concurrently by bcm_rx_setup(). ++ */ ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ rtr_frame = op->flags & RX_RTR_FRAME; ++ if (rtr_frame) ++ memcpy(&rtrframe, op->frames, op->cfsiz); ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ +- bcm_can_tx(op); ++ bcm_can_tx(op, &rtrframe); + return; + } + +@@ -738,6 +777,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe, +@@ -773,6 +814,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + + rx_starttimer: + bcm_rx_starttimer(op); ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + /* +@@ -1115,7 +1158,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + list_add_rcu(&op->list, &bo->tx_ops); + + if (op->flags & TX_ANNOUNCE) +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + + if (op->flags & STARTTIMER) + bcm_tx_start_timer(op); +@@ -1129,6 +1172,24 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + ++static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) ++{ ++ /* funny feature in RX(!)_SETUP only for RTR-mode: ++ * copy can_id into frame BUT without RTR-flag to ++ * prevent a full-load-loopback-test ... ;-] ++ * normalize this on the staged buffer, before it is ++ * ever installed into op->frames. ++ */ ++ if (msg_head->flags & RX_RTR_FRAME) { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; ++ } ++} ++ + /* + * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg) + */ +@@ -1163,6 +1224,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->rx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames = NULL; ++ + /* update existing BCM operation */ + + /* +@@ -1174,19 +1237,48 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -E2BIG; + + if (msg_head->nframes) { +- /* update CAN frames content */ +- err = memcpy_from_msg(op->frames, msg, ++ /* get new CAN frames content before locking */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, ++ GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ ++ err = memcpy_from_msg(new_frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + +- /* clear last_frames to indicate 'nothing received' */ +- memset(op->last_frames, 0, msg_head->nframes * op->cfsiz); ++ bcm_rx_setup_rtr_check(msg_head, op, new_frames); + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); + op->nframes = msg_head->nframes; + op->flags = msg_head->flags; + ++ if (msg_head->nframes) { ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, ++ msg_head->nframes * op->cfsiz); ++ ++ /* clear last_frames to indicate 'nothing received' */ ++ memset(op->last_frames, 0, ++ msg_head->nframes * op->cfsiz); ++ } ++ ++ if (msg_head->flags & SETTIMER) { ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ } ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ /* free temporary frames / kfree(NULL) is safe */ ++ kfree(new_frames); ++ + /* Only an update -> do not call can_rx_register() */ + do_rx_register = 0; + +@@ -1197,6 +1289,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -ENOMEM; + + spin_lock_init(&op->bcm_tx_lock); ++ spin_lock_init(&op->bcm_rx_update_lock); + op->can_id = msg_head->can_id; + op->nframes = msg_head->nframes; + op->cfsiz = CFSIZ(msg_head->flags); +@@ -1238,6 +1331,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + kfree(op); + return err; + } ++ ++ bcm_rx_setup_rtr_check(msg_head, op, op->frames); + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1262,29 +1357,22 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check flags */ + + if (op->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = op->frames; +- + /* no timers in RTR-mode */ + hrtimer_cancel(&op->thrtimer); + hrtimer_cancel(&op->timer); +- +- /* +- * funny feature in RX(!)_SETUP only for RTR-mode: +- * copy can_id into frame BUT without RTR-flag to +- * prevent a full-load-loopback-test ... ;-] +- */ +- if ((op->flags & TX_CP_CAN_ID) || +- (frame0->can_id == op->can_id)) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- + } else { + if (op->flags & SETTIMER) { + +- /* set timer value */ +- op->ival1 = msg_head->ival1; +- op->ival2 = msg_head->ival2; +- op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); +- op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ /* set timers (locked) for newly created op */ ++ if (do_rx_register) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } + + /* disable an active timer due to zero value? */ + if (!op->kt_ival1) +@@ -1294,9 +1382,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + * In any case cancel the throttle timer, flush + * potentially blocked msgs and reset throttle handling + */ +- op->kt_lastmsg = 0; + hrtimer_cancel(&op->thrtimer); ++ ++ spin_lock_bh(&op->bcm_rx_update_lock); + bcm_rx_thr_flush(op); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + if ((op->flags & STARTTIMER) && op->kt_ival1) +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-add-missing-device-refcount-for-can-filter-r.patch b/queue-6.18/can-bcm-add-missing-device-refcount-for-can-filter-r.patch new file mode 100644 index 0000000000..92bfeef830 --- /dev/null +++ b/queue-6.18/can-bcm-add-missing-device-refcount-for-can-filter-r.patch @@ -0,0 +1,121 @@ +From 74de3f65c64ce523552a5b76cd0bbef583942a9e Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:55 +0200 +Subject: can: bcm: add missing device refcount for CAN filter removal + +From: Oliver Hartkopp + +commit d59948293ea34b6337ce2b5febab8510de70048c upstream. + +sashiko-bot remarked a problem with a concurrent device unregistration +in isotp.c which also is present in the bcm.c code. A former fix for raw.c +commit c275a176e4b6 ("can: raw: add missing refcount for memory leak fix") +introduced a netdevice_tracker which solves the issue for bcm.c too. + +bcm_release(), bcm_delete_rx_op() and bcm_notifier() relied on +dev_get_by_index(ifindex) to re-find the device for an rx_op before +unregistering its filter. If a concurrent NETDEV_UNREGISTER has already +unlisted the device from the ifindex table, that lookup fails and +can_rx_unregister() is silently skipped, leaving a stale CAN filter +pointing at the soon-to-be-freed bcm_op/socket. + +Hold a netdev_hold()/netdev_put() tracked reference on op->rx_reg_dev +from the moment the rx filter is registered in bcm_rx_setup() until it +is unregistered in bcm_rx_unreg(), and use that reference directly in +bcm_release() and bcm_delete_rx_op() instead of re-looking the device +up by ifindex. + +Reported-by: sashiko-bot@kernel.org +Closes: https://sashiko.dev/#/patchset/20260707094716.63578-1-socketcan@hartkopp.net +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 44 ++++++++++++++++++++++++-------------------- + 1 file changed, 24 insertions(+), 20 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 2de64d7a99a216..ca0dd2229f5b86 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -932,6 +932,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op) + + /* mark as removed subscription */ + op->rx_reg_dev = NULL; ++ dev_put(dev); + } else + printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device " + "mismatch %p %p\n", op->rx_reg_dev, dev); +@@ -962,17 +963,14 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh, + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the dev. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(sock_net(op->sk), +- op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(sock_net(op->sk), NULL, + op->can_id, +@@ -1491,7 +1489,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + bcm_rx_handler, op, + "bcm", sk); + +- op->rx_reg_dev = dev; ++ /* keep a reference so that a later ++ * unregister can safely reach the device even ++ * if a concurrent NETDEV_UNREGISTER has ++ * already unlisted it by ifindex ++ */ ++ if (!err) { ++ op->rx_reg_dev = dev; ++ dev_hold(dev); ++ } + dev_put(dev); + } else { + /* the requested device is gone - do not +@@ -1864,16 +1870,14 @@ static int bcm_release(struct socket *sock) + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the device. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(net, op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(net, NULL, op->can_id, + REGMASK(op->can_id), +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch b/queue-6.18/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch new file mode 100644 index 0000000000..b5dbac890d --- /dev/null +++ b/queue-6.18/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch @@ -0,0 +1,230 @@ +From e7f8d6dc3fc7ddbd12fbaa6327b4ad145fd33e76 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:53 +0200 +Subject: can: bcm: extend bcm_tx_lock usage for data and timer updates + +From: Oliver Hartkopp + +commit 12ce799f7ab1e05bd8fbf79e46f403bfe5597ebc upstream. + +Stage new CAN frame content for an existing tx op into a kmalloc()'d +buffer and validate it there, mirroring the approach already used in +bcm_rx_setup(). Only copy the validated data into op->frames while +holding op->bcm_tx_lock, so bcm_can_tx() and bcm_tx_timeout_handler() +can no longer observe a partially updated or unvalidated frame. + +Add a missing error path for memcpy_from_msg() when copying CAN frame +data from userspace. + +Also move the kt_ival1/kt_ival2/ival1/ival2 updates in bcm_tx_setup() +under op->bcm_tx_lock, and read kt_ival1/kt_ival2/count under the same +lock in bcm_tx_set_expiry() and bcm_tx_timeout_handler(), closing the +torn 64-bit ktime_t read on 32-bit platforms. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 104 ++++++++++++++++++++++++++++++++++++-------------- + 1 file changed, 75 insertions(+), 29 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 1d4323cdf48a47..d7f91afda3c85d 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -127,7 +127,7 @@ struct bcm_op { + struct canfd_frame last_sframe; + struct sock *sk; + struct net_device *rx_reg_dev; +- spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_tx_lock; /* protect tx data and timer updates */ + spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + +@@ -467,12 +467,18 @@ static bool bcm_tx_set_expiry(struct bcm_op *op, struct hrtimer *hrt) + { + ktime_t ival; + ++ spin_lock_bh(&op->bcm_tx_lock); ++ + if (op->kt_ival1 && op->count) + ival = op->kt_ival1; +- else if (op->kt_ival2) ++ else if (op->kt_ival2) { + ival = op->kt_ival2; +- else ++ } else { ++ spin_unlock_bh(&op->bcm_tx_lock); + return false; ++ } ++ ++ spin_unlock_bh(&op->bcm_tx_lock); + + hrtimer_set_expires(hrt, ktime_add(ktime_get(), ival)); + return true; +@@ -489,25 +495,47 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; ++ bool tx_ival1, tx_ival2; ++ ++ /* snapshot kt_ival1/kt_ival2/count under lock to avoid torn ++ * ktime_t reads racing with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ tx_ival1 = op->kt_ival1 && (op->count > 0); ++ tx_ival2 = !!op->kt_ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (tx_ival1) { ++ u32 flags, count; ++ struct bcm_timeval ival1, ival2; + +- if (op->kt_ival1 && (op->count > 0)) { + bcm_can_tx(op, NULL); +- if (!op->count && (op->flags & TX_COUNTEVT)) { + ++ /* snapshot variables under lock to avoid torn reads racing ++ * with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ flags = op->flags; ++ count = op->count; ++ ival1 = op->ival1; ++ ival2 = op->ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (!count && (flags & TX_COUNTEVT)) { + /* create notification to user */ + memset(&msg_head, 0, sizeof(msg_head)); + msg_head.opcode = TX_EXPIRED; +- msg_head.flags = op->flags; +- msg_head.count = op->count; +- msg_head.ival1 = op->ival1; +- msg_head.ival2 = op->ival2; ++ msg_head.flags = flags; ++ msg_head.count = count; ++ msg_head.ival1 = ival1; ++ msg_head.ival2 = ival2; + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + + bcm_send_to_user(op, &msg_head, NULL, 0); + } + +- } else if (op->kt_ival2) { ++ } else if (tx_ival2) { + bcm_can_tx(op, NULL); + } + +@@ -1031,6 +1059,8 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->tx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames; ++ + /* update existing BCM operation */ + + /* +@@ -1041,11 +1071,23 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes > op->nframes) + return -E2BIG; + +- /* update CAN frames content */ ++ /* get new CAN frames content into a staging buffer before ++ * locking: validate and normalize the frames there so that ++ * bcm_can_tx() / bcm_tx_timeout_handler() never observe a ++ * partially updated or unvalidated frame in op->frames ++ */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ + for (i = 0; i < msg_head->nframes; i++) { + +- cf = op->frames + op->cfsiz * i; ++ cf = new_frames + op->cfsiz * i; + err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + + if (op->flags & CAN_FD_FRAME) { + if (cf->len > 64) +@@ -1055,36 +1097,38 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -EINVAL; + } + +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + + if (msg_head->flags & TX_CP_CAN_ID) { + /* copy can_id into frame */ + cf->can_id = msg_head->can_id; + } + } ++ ++ spin_lock_bh(&op->bcm_tx_lock); ++ ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, msg_head->nframes * op->cfsiz); ++ + op->flags = msg_head->flags; + +- /* only lock for unlikely count/nframes/currframe changes */ + if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX || +- op->flags & SETTIMER) { +- +- spin_lock_bh(&op->bcm_tx_lock); ++ op->flags & TX_RESET_MULTI_IDX) { ++ /* potentially update changed nframes */ ++ op->nframes = msg_head->nframes; ++ /* restart multiple frame transmission */ ++ op->currframe = 0; ++ } + +- if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX) { +- /* potentially update changed nframes */ +- op->nframes = msg_head->nframes; +- /* restart multiple frame transmission */ +- op->currframe = 0; +- } ++ if (op->flags & SETTIMER) ++ op->count = msg_head->count; + +- if (op->flags & SETTIMER) +- op->count = msg_head->count; ++ spin_unlock_bh(&op->bcm_tx_lock); + +- spin_unlock_bh(&op->bcm_tx_lock); +- } ++ kfree(new_frames); + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1160,10 +1204,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + if (op->flags & SETTIMER) { + /* set timer values */ ++ spin_lock_bh(&op->bcm_tx_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ spin_unlock_bh(&op->bcm_tx_lock); + + /* disable an active timer due to zero values? */ + if (!op->kt_ival1 && !op->kt_ival2) +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-fix-can-frame-rx-tx-statistics.patch b/queue-6.18/can-bcm-fix-can-frame-rx-tx-statistics.patch new file mode 100644 index 0000000000..cec7329503 --- /dev/null +++ b/queue-6.18/can-bcm-fix-can-frame-rx-tx-statistics.patch @@ -0,0 +1,200 @@ +From d7eada5cb9a07fa7443a5cac9ea8e86697cf4744 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:52 +0200 +Subject: can: bcm: fix CAN frame rx/tx statistics + +From: Oliver Hartkopp + +commit e6c24ba95fc3f1b5e1dcd28b1c6e59ef61a9daa5 upstream. + +KCSAN detected a data race within the bcm_rx_handler() when two CAN frames +have been simultaneously received and processed in a single rx op by two +different CPUs. + +Use atomic operations with (signed) long data types to access the +statistics in the hot path to fix the KCSAN complaint. + +Additionally simplify the update and check of statistics overflow by +using the atomic operations in separate bcm_update_[rx|tx]_stats() +functions. The rx variant runs under bcm_rx_update_lock to prevent +races when resetting the two rx counters; the tx variant runs under +bcm_tx_lock and only needs to guard its own counter's overflow. + +As the rx path resets its values already at LONG_MAX / 100, there is +no conflict between the two locking domains (bcm_rx_update_lock vs. +bcm_tx_lock) even for ops that use both paths. + +The rx statistics update and the frames_filtered update in +bcm_rx_changed() were previously performed in two separate +bcm_rx_update_lock sections. For an rx op subscribed on all interfaces +(ifindex == 0), bcm_rx_handler() can run concurrently on different +CPUs, so a counter reset by one CPU between these two sections could +leave frames_filtered larger than frames_abs on another CPU, producing +a bogus (even negative) reduction percentage in procfs. Update the +statistics in the same critical section as bcm_rx_changed() to close +this gap, which also removes the now unneeded extra lock/unlock pair +around the traffic_flags calculation. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 69 ++++++++++++++++++++++++++++++++++----------------- + 1 file changed, 46 insertions(+), 23 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index cfc495106aac4d..1d4323cdf48a47 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -111,7 +111,7 @@ struct bcm_op { + int ifindex; + canid_t can_id; + u32 flags; +- unsigned long frames_abs, frames_filtered; ++ atomic_long_t frames_abs, frames_filtered; + struct bcm_timeval ival1, ival2; + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; +@@ -228,10 +228,13 @@ static int bcm_proc_show(struct seq_file *m, void *v) + + list_for_each_entry_rcu(op, &bo->rx_ops, list) { + +- unsigned long reduction; ++ long reduction, frames_filtered, frames_abs; ++ ++ frames_filtered = atomic_long_read(&op->frames_filtered); ++ frames_abs = atomic_long_read(&op->frames_abs); + + /* print only active entries & prevent division by zero */ +- if (!op->frames_abs) ++ if (!frames_abs) + continue; + + seq_printf(m, "rx_op: %03X %-5s ", op->can_id, +@@ -253,9 +256,9 @@ static int bcm_proc_show(struct seq_file *m, void *v) + (long long)ktime_to_us(op->kt_ival2)); + + seq_printf(m, "# recv %ld (%ld) => reduction: ", +- op->frames_filtered, op->frames_abs); ++ frames_filtered, frames_abs); + +- reduction = 100 - (op->frames_filtered * 100) / op->frames_abs; ++ reduction = 100 - (frames_filtered * 100) / frames_abs; + + seq_printf(m, "%s%ld%%\n", + (reduction == 100) ? "near " : "", reduction); +@@ -279,7 +282,8 @@ static int bcm_proc_show(struct seq_file *m, void *v) + seq_printf(m, "t2=%lld ", + (long long)ktime_to_us(op->kt_ival2)); + +- seq_printf(m, "# sent %ld\n", op->frames_abs); ++ seq_printf(m, "# sent %ld\n", ++ atomic_long_read(&op->frames_abs)); + } + seq_putc(m, '\n'); + +@@ -289,6 +293,24 @@ static int bcm_proc_show(struct seq_file *m, void *v) + } + #endif /* CONFIG_PROC_FS */ + ++static void bcm_update_rx_stats(struct bcm_op *op) ++{ ++ /* prevent overflow of the reduction% calculation in bcm_proc_show() */ ++ if (atomic_long_inc_return(&op->frames_abs) > LONG_MAX / 100) { ++ atomic_long_set(&op->frames_filtered, 0); ++ atomic_long_set(&op->frames_abs, 0); ++ } ++} ++ ++static void bcm_update_tx_stats(struct bcm_op *op) ++{ ++ /* tx_op has no reduction% calculation - use the full range and ++ * just keep the displayed counter non-negative on overflow ++ */ ++ if (atomic_long_inc_return(&op->frames_abs) == LONG_MAX) ++ atomic_long_set(&op->frames_abs, 0); ++} ++ + /* + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op +@@ -340,7 +362,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + spin_lock_bh(&op->bcm_tx_lock); + + if (!err) +- op->frames_abs++; ++ bcm_update_tx_stats(op); + + /* only advance the cyclic sequence if nothing reset currframe while + * we were sending - a concurrent TX_RESET_MULTI_IDX means this +@@ -500,12 +522,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data) + { + struct bcm_msg_head head; + +- /* update statistics */ +- op->frames_filtered++; +- +- /* prevent statistics overflow */ +- if (op->frames_filtered > ULONG_MAX/100) +- op->frames_filtered = op->frames_abs = 0; ++ /* update statistics (frames_filtered <= frames_abs) */ ++ if (atomic_long_read(&op->frames_abs)) ++ atomic_long_inc(&op->frames_filtered); + + /* this element is not throttled anymore */ + data->flags &= ~RX_THR; +@@ -751,24 +770,30 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + op->rx_stamp = skb->tstamp; + /* save originator for recvfrom() */ + op->rx_ifindex = skb->dev->ifindex; +- /* update statistics */ +- op->frames_abs++; + +- /* snapshot the flag under lock: op->flags/op->frames may be updated +- * concurrently by bcm_rx_setup(). +- */ ++ /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); ++ + rtr_frame = op->flags & RX_RTR_FRAME; +- if (rtr_frame) ++ if (rtr_frame) { ++ bcm_update_rx_stats(op); ++ /* snapshot RTR content under lock */ + memcpy(&rtrframe, op->frames, op->cfsiz); +- spin_unlock_bh(&op->bcm_rx_update_lock); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + +- if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ + bcm_can_tx(op, &rtrframe); + return; + } + ++ /* update statistics in the same critical section as bcm_rx_changed() ++ * below: frames_filtered must never be checked/incremented against a ++ * frames_abs snapshot from a concurrent bcm_rx_handler() call on ++ * another CPU for the same (wildcard) op, or frames_filtered can end ++ * up larger than frames_abs. ++ */ ++ bcm_update_rx_stats(op); ++ + /* compute flags to distinguish between own/local/remote CAN traffic */ + traffic_flags = 0; + if (skb->sk) { +@@ -777,8 +802,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + +- spin_lock_bh(&op->bcm_rx_update_lock); +- + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe, +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch b/queue-6.18/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch new file mode 100644 index 0000000000..0bbfc6c731 --- /dev/null +++ b/queue-6.18/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch @@ -0,0 +1,74 @@ +From b6e0abd360418888050fb72725c62ca279e246c7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:57 +0200 +Subject: can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler() + +From: Oliver Hartkopp + +commit 58fd6cbc8541216af1d7ed272ea7ac2b66d50fd8 upstream. + +For an rx op subscribed on all interfaces (ifindex == 0), the same op +is registered once in the shared per-netns wildcard filter list, so +bcm_rx_handler() can run concurrently on different CPUs for frames +arriving on different net devices. + +op->rx_stamp and op->rx_ifindex were written before bcm_rx_update_lock was +taken, allowing concurrent writers to race each other - including a torn +store of the 64-bit rx_stamp on 32-bit platforms. + +Beyond a torn store bcm_send_to_user() must report the timestamp/ifindex +of the very same frame whose content it is delivering. So the assignment +is placed in the same unbroken bcm_rx_update_lock section as the content +comparison. + +As a side effect, the RTR-request frame feature (which never reach +bcm_send_to_user()) no longer updates rx_stamp/rx_ifindex, since only +the notification path needs them. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260707145135.5BC831F00A3A@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 8c138376591f37..079a6a801f42e3 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -794,11 +794,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* disable timeout */ + hrtimer_cancel(&op->timer); + +- /* save rx timestamp */ +- op->rx_stamp = skb->tstamp; +- /* save originator for recvfrom() */ +- op->rx_ifindex = skb->dev->ifindex; +- + /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); + +@@ -830,6 +825,14 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + ++ /* save rx timestamp and originator for recvfrom() under lock. ++ * For an op subscribed on all interfaces (ifindex == 0) ++ * bcm_rx_handler() can run concurrently on different CPUs so ++ * the CAN content and the meta data must be bundled correctly. ++ */ ++ op->rx_stamp = skb->tstamp; ++ op->rx_ifindex = skb->dev->ifindex; ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe, +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch b/queue-6.18/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch new file mode 100644 index 0000000000..e418314b7b --- /dev/null +++ b/queue-6.18/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch @@ -0,0 +1,164 @@ +From 9fed2cc86e0fcdb4bff58d1a5b8f8297ba1d114d Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:56 +0200 +Subject: can: bcm: fix stale rx/tx ops after device removal + +From: Oliver Hartkopp + +commit 3b762c0d950383ab7a002686c9136b9aa55d2d70 upstream. + +RX: an RX_SETUP update(!) for an existing op skipped can_rx_register() +unconditionally, even when a concurrent NETDEV_UNREGISTER had already +torn down its registration (op->rx_reg_dev == NULL). This silently +did not re-enable frame delivery for that updated filter. bcm_rx_setup() +now re-registers in that case, while leaving rx_ops with ifindex = 0 +(all CAN devices) which never carry a tracked rx_reg_dev registered as-is. + +TX: bcm_notify() only handled bo->rx_ops on NETDEV_UNREGISTER, leaving +tx_ops with an active cyclic transmission re-arming its hrtimer +indefinitely to execute bcm_tx_timeout_handler(). Cancelling the hrtimer +prevents the runaway timer and any injection into a later reused ifindex, +since nothing else calls bcm_can_tx() for the op until an explicit +TX_SETUP update re-arms it. + +Unlike bcm_rx_unreg(), which clears the tracked rx_reg_dev for rx_ops, +the ifindex is intentionally left unchanged for tx_ops. bcm_tx_setup() +always rejects ifindex 0, so clearing it would strand the op: neither a +later TX_SETUP (bcm_find_op()) nor TX_DELETE (bcm_delete_tx_op()) could +ever find it again, since both require an exact ifindex match. + +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260708094536.DDF821F00A3A@smtp.kernel.org/ +Closes: https://lore.kernel.org/linux-can/20260708154039.347ED1F000E9@smtp.kernel.org/ +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 54 +++++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 44 insertions(+), 10 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index ca0dd2229f5b86..8c138376591f37 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1281,6 +1281,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + struct bcm_sock *bo = bcm_sk(sk); + struct bcm_op *op; + int do_rx_register; ++ int new_op = 0; + int err = 0; + + if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) { +@@ -1365,8 +1366,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* free temporary frames / kfree(NULL) is safe */ + kfree(new_frames); + +- /* Only an update -> do not call can_rx_register() */ +- do_rx_register = 0; ++ /* Don't register a new CAN filter for the rx_op update unless ++ * a concurrent NETDEV_UNREGISTER notifier already tore down ++ * the previous registration. In this case the receiver needs ++ * to be re-registered here so that this update doesn't ++ * silently stop delivering frames for the given ifindex. ++ * Ops with ifindex = 0 (all CAN interfaces) never carry a ++ * tracked rx_reg_dev and stay registered as-is. ++ */ ++ do_rx_register = (ifindex && !op->rx_reg_dev) ? 1 : 0; + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1433,6 +1441,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + /* call can_rx_register() */ + do_rx_register = 1; ++ new_op = 1; + + } /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */ + +@@ -1446,7 +1455,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (op->flags & SETTIMER) { + + /* set timers (locked) for newly created op */ +- if (do_rx_register) { ++ if (new_op) { + spin_lock_bh(&op->bcm_rx_update_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; +@@ -1476,7 +1485,10 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + HRTIMER_MODE_REL_SOFT); + } + +- /* now we can register for can_ids, if we added a new bcm_op */ ++ /* now we can register for can_ids, if we added a new bcm_op ++ * or need to re-register after a NETDEV_UNREGISTER tore down ++ * the previous registration of an existing op ++ */ + if (do_rx_register) { + if (ifindex) { + struct net_device *dev; +@@ -1506,18 +1518,32 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -ENODEV; + } + +- } else ++ } else { + err = can_rx_register(sock_net(sk), NULL, op->can_id, + REGMASK(op->can_id), + bcm_rx_handler, op, "bcm", sk); ++ } ++ + if (err) { +- /* this bcm rx op is broken -> remove it */ +- bcm_remove_op(op); ++ /* newly created bcm rx op is broken -> remove it */ ++ if (new_op) { ++ bcm_remove_op(op); ++ return err; ++ } ++ ++ /* an existing op just stays unregistered. ++ * Cancel op->timer and (defensively) op->thrtimer. ++ * Other settings can't be reached until the next ++ * successful RX_SETUP. ++ */ ++ hrtimer_cancel(&op->timer); ++ hrtimer_cancel(&op->thrtimer); + return err; + } + +- /* add this bcm_op to the list of the rx_ops */ +- list_add_rcu(&op->list, &bo->rx_ops); ++ /* add a new bcm_op to the list of the rx_ops */ ++ if (new_op) ++ list_add_rcu(&op->list, &bo->rx_ops); + } + + return msg_head->nframes * op->cfsiz + MHSIZ; +@@ -1733,11 +1759,19 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + case NETDEV_UNREGISTER: + lock_sock(sk); + +- /* remove device specific receive entries */ ++ /* rx_ops: remove device specific receive entries */ + list_for_each_entry(op, &bo->rx_ops, list) + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* tx_ops: stop device specific cyclic transmissions on the ++ * vanishing ifindex. Cancelling the timer is enough to stop ++ * cyclic bcm_can_tx() calls as there is no re-arming. ++ */ ++ list_for_each_entry(op, &bo->tx_ops, list) ++ if (op->ifindex == dev->ifindex) ++ hrtimer_cancel(&op->timer); ++ + /* remove device reference, if this is our bound device */ + if (bo->bound && bo->ifindex == dev->ifindex) { + #if IS_ENABLED(CONFIG_PROC_FS) +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-track-a-single-source-interface-for-anydev-t.patch b/queue-6.18/can-bcm-track-a-single-source-interface-for-anydev-t.patch new file mode 100644 index 0000000000..f8373cf531 --- /dev/null +++ b/queue-6.18/can-bcm-track-a-single-source-interface-for-anydev-t.patch @@ -0,0 +1,143 @@ +From c0cf15359cc3d0d3ddbc8aaaf413dfdbd784f12f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:58 +0200 +Subject: can: bcm: track a single source interface for ANYDEV timeout/throttle + ops + +From: Oliver Hartkopp + +commit 2f5976f54a04e9f18b25283036ac3136be453b17 upstream. + +An ANYDEV rx op (ifindex == 0) with an active RX timeout and/or +throttle timer has no defined semantics when matching frames arrive +from several interfaces: bcm_rx_handler() can run concurrently for +the same op on different CPUs, racing hrtimer_cancel()/ +bcm_rx_starttimer() against bcm_rx_timeout_handler() and causing +spurious RX_TIMEOUT notifications and last_frames corruption. The +same concurrency lets throttled multiplex frames from different +interfaces clobber the single rx_ifindex/rx_stamp fields shared by +the op. + +Add op->if_detected to track the first interface that delivers a +matching frame while a timeout/throttle timer is configured, and +reject frames from any other interface for that op. The claim is +decided in bcm_rx_handler() before hrtimer_cancel() touches +op->timer, so a rejected frame can never disturb the claimed +interface's watchdog. RTR-mode ops are excluded via RX_RTR_FRAME, +independent of kt_ival1/kt_ival2, since those may briefly hold a +stale value from an earlier non-RTR configuration. + +The claim is released in bcm_notify() on NETDEV_UNREGISTER and in +bcm_rx_setup() when SETTIMER reconfigures the timer values. + +A (re-)claim is only possible on CAN devices in NETREG_REGISTERED +dev->reg_state to cover the release in bcm_notify() where reg_state +becomes NETREG_UNREGISTERING until synchronize_net(). + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260709105031.1A39C1F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 44 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 079a6a801f42e3..76bc7f72742535 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -116,6 +116,7 @@ struct bcm_op { + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; + int rx_ifindex; ++ int if_detected; /* first received ifindex in ANYDEV rx_op mode */ + int cfsiz; + u32 count; + u32 nframes; +@@ -791,6 +792,33 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + return; + } + ++ /* An ANYDEV op with an active RX timeout and/or throttle timer ++ * tracks a single source interface: claim the first interface that ++ * delivers a matching frame and reject frames from any other one, ++ * before hrtimer_cancel() below can touch op->timer - this avoids ++ * racing bcm_rx_timeout_handler() across concurrent interfaces. ++ * RX_RTR_FRAME ops are excluded, as kt_ival1/kt_ival2 may briefly ++ * hold a stale value from an earlier non-RTR configuration. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ if (!(op->flags & RX_RTR_FRAME) && ++ (op->kt_ival1 || op->kt_ival2)) { ++ /* don't claim to vanishing interface */ ++ if (!op->if_detected && ++ skb->dev->reg_state == NETREG_REGISTERED) ++ op->if_detected = skb->dev->ifindex; ++ ++ if (op->if_detected != skb->dev->ifindex) { ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ return; ++ } ++ } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ + /* disable timeout */ + hrtimer_cancel(&op->timer); + +@@ -825,10 +853,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + traffic_flags |= RX_OWN; + } + +- /* save rx timestamp and originator for recvfrom() under lock. +- * For an op subscribed on all interfaces (ifindex == 0) +- * bcm_rx_handler() can run concurrently on different CPUs so +- * the CAN content and the meta data must be bundled correctly. ++ /* save rx timestamp and originator for recvfrom() under lock: an ++ * ANYDEV op without an active timer can still run concurrently on ++ * different CPUs, so content and meta data must be bundled here. + */ + op->rx_stamp = skb->tstamp; + op->rx_ifindex = skb->dev->ifindex; +@@ -1363,6 +1390,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + op->kt_lastmsg = 0; ++ op->if_detected = 0; /* reclaim ifindex in ANYDEV mode */ + } + spin_unlock_bh(&op->bcm_rx_update_lock); + +@@ -1763,10 +1791,21 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + lock_sock(sk); + + /* rx_ops: remove device specific receive entries */ +- list_for_each_entry(op, &bo->rx_ops, list) ++ list_for_each_entry(op, &bo->rx_ops, list) { + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* release an ANYDEV op's claim (see bcm_rx_handler()) ++ * on this now confirmed-gone interface. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ if (op->if_detected == dev->ifindex) ++ op->if_detected = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ } ++ + /* tx_ops: stop device specific cyclic transmissions on the + * vanishing ifindex. Cancelling the timer is enough to stop + * cyclic bcm_can_tx() calls as there is no re-arming. +-- +2.53.0 + diff --git a/queue-6.18/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch b/queue-6.18/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch new file mode 100644 index 0000000000..fbc2dd0683 --- /dev/null +++ b/queue-6.18/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch @@ -0,0 +1,128 @@ +From 658d180b11123909f7d092e5ca6e5349b9c3764c Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:25:54 +0200 +Subject: can: bcm: validate frame length in bcm_rx_setup() for RTR replies + +From: Oliver Hartkopp + +commit 62ec41f364648be79d54d94d0d240ee326948afd upstream. + +bcm_tx_setup() validates cf->len against the CAN/CAN FD DLC limits +before installing frames for TX_SETUP, but bcm_rx_setup() never did +the same for the RTR-reply frame configured via RX_SETUP with +RX_RTR_FRAME. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++---------------- + 1 file changed, 41 insertions(+), 18 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index d7f91afda3c85d..2de64d7a99a216 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1241,22 +1241,37 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +-static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, +- struct bcm_op *op, void *new_frames) ++static int bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) + { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if (!(msg_head->flags & RX_RTR_FRAME)) ++ return 0; ++ ++ /* this frame is sent out as-is by bcm_can_tx() whenever a matching ++ * remote request is received, so validate its length the same way ++ * bcm_tx_setup() validates TX_SETUP frames before installing it ++ */ ++ if (msg_head->flags & CAN_FD_FRAME) { ++ if (frame0->len > 64) ++ return -EINVAL; ++ } else { ++ if (frame0->len > 8) ++ return -EINVAL; ++ } ++ + /* funny feature in RX(!)_SETUP only for RTR-mode: + * copy can_id into frame BUT without RTR-flag to + * prevent a full-load-loopback-test ... ;-] + * normalize this on the staged buffer, before it is + * ever installed into op->frames. + */ +- if (msg_head->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = new_frames; ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; + +- if ((msg_head->flags & TX_CP_CAN_ID) || +- frame0->can_id == op->can_id) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- } ++ return 0; + } + + /* +@@ -1319,7 +1334,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +- bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + } + + spin_lock_bh(&op->bcm_rx_update_lock); +@@ -1392,16 +1411,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes) { + err = memcpy_from_msg(op->frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) { +- if (op->frames != &op->sframe) +- kfree(op->frames); +- if (op->last_frames != &op->last_sframe) +- kfree(op->last_frames); +- kfree(op); +- return err; +- } ++ if (err < 0) ++ goto free_op; + +- bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ if (err < 0) ++ goto free_op; + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1500,6 +1515,14 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + } + + return msg_head->nframes * op->cfsiz + MHSIZ; ++ ++free_op: ++ if (op->frames != &op->sframe) ++ kfree(op->frames); ++ if (op->last_frames != &op->last_sframe) ++ kfree(op->last_frames); ++ kfree(op); ++ return err; + } + + /* +-- +2.53.0 + diff --git a/queue-6.18/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch b/queue-6.18/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch new file mode 100644 index 0000000000..5b5b6aa4a5 --- /dev/null +++ b/queue-6.18/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch @@ -0,0 +1,44 @@ +From c7e4826832000515d3dd46bb8db232747d54ba1e Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 15 May 2026 11:34:52 +0900 +Subject: crypto: tegra - Don't touch bo refcount in host1x bo pin/unpin + +From: Mikko Perttunen + +[ Upstream commit f8c9c57d750346abd213ffed2ae3cacb0268e9f1 ] + +Since commit "gpu: host1x: Allow entries in BO caches to be freed", +host1x_bo_pin() and host1x_bo_unpin() handle the bo's refcount +themselves. .pin/.unpin callbacks should not adjust it. + +Signed-off-by: Mikko Perttunen +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +--- + drivers/crypto/tegra/tegra-se-main.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/drivers/crypto/tegra/tegra-se-main.c b/drivers/crypto/tegra/tegra-se-main.c +index 2755f19ad05e92..77b923a6b7c18c 100644 +--- a/drivers/crypto/tegra/tegra-se-main.c ++++ b/drivers/crypto/tegra/tegra-se-main.c +@@ -52,7 +52,7 @@ tegra_se_cmdbuf_pin(struct device *dev, struct host1x_bo *bo, enum dma_data_dire + return ERR_PTR(-ENOMEM); + + kref_init(&map->ref); +- map->bo = host1x_bo_get(bo); ++ map->bo = bo; + map->direction = direction; + map->dev = dev; + +@@ -93,7 +93,6 @@ static void tegra_se_cmdbuf_unpin(struct host1x_bo_mapping *map) + dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0); + sg_free_table(map->sgt); + kfree(map->sgt); +- host1x_bo_put(map->bo); + + kfree(map); + } +-- +2.53.0 + diff --git a/queue-6.18/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch b/queue-6.18/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch new file mode 100644 index 0000000000..8e3b3a3b7a --- /dev/null +++ b/queue-6.18/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch @@ -0,0 +1,203 @@ +From 5a500602f6dccea021f20ab5af3648547a07b43f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 14:59:09 +0200 +Subject: dmaengine: sh: rz-dmac: Move interrupt request after everything is + set up + +From: Claudiu Beznea + +commit 731712403ddb39d1a76a11abf339a0615bc85de7 upstream. + +Once the interrupt is requested, the interrupt handler may run immediately. +Since the IRQ handler can access channel->ch_base, which is initialized +only after requesting the IRQ, this may lead to invalid memory access. +Likewise, the IRQ thread may access uninitialized data (the ld_free, +ld_queue, and ld_active lists), which may also lead to issues. + +Request the interrupts only after everything is set up. To keep the error +path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent(). + +Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC") +Cc: stable@vger.kernel.org +Reviewed-by: Frank Li +Tested-by: John Madieu +Signed-off-by: Claudiu Beznea +Tested-by: Tommaso Merciai +Link: https://patch.msgid.link/20260526084710.3491480-2-claudiu.beznea@kernel.org +Signed-off-by: Vinod Koul +[tm: Kept the channel->irq field in rz_dmac_chan_probe() instead of + upstream's local `irq` variable, as commit 04e227718ab8 + ("dmaengine: sh: rz-dmac: Make channel irq local") is not present + in this tree. Likewise kept platform_get_irq_byname() instead of + platform_get_irq_byname_optional() for the error IRQ in rz_dmac_probe(), + as commit 6b3a6b6dc074 ("dmaengine: sh: rz_dmac: make error interrupt + optional") is not present in this tree either; its early return on + failure becomes a goto err jump to match the new call order.] +Signed-off-by: Tommaso Merciai +Signed-off-by: Sasha Levin +--- + drivers/dma/sh/rz-dmac.c | 96 ++++++++++++++++------------------------ + 1 file changed, 38 insertions(+), 58 deletions(-) + +diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c +index 818d1ef6f0bf94..4db84b83177a6d 100644 +--- a/drivers/dma/sh/rz-dmac.c ++++ b/drivers/dma/sh/rz-dmac.c +@@ -811,27 +811,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + channel->index = index; + channel->mid_rid = -EINVAL; + +- /* Request the channel interrupt. */ +- scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index); +- channel->irq = platform_get_irq_byname(pdev, pdev_irqname); +- if (channel->irq < 0) +- return channel->irq; +- +- irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", +- dev_name(dmac->dev), index); +- if (!irqname) +- return -ENOMEM; +- +- ret = devm_request_threaded_irq(dmac->dev, channel->irq, +- rz_dmac_irq_handler, +- rz_dmac_irq_handler_thread, 0, +- irqname, channel); +- if (ret) { +- dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", +- channel->irq, ret); +- return ret; +- } +- + /* Set io base address for each channel */ + if (index < 8) { + channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET + +@@ -844,9 +823,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + } + + /* Allocate descriptors */ +- lmdesc = dma_alloc_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- &channel->lmdesc.base_dma, GFP_KERNEL); ++ lmdesc = dmam_alloc_coherent(&pdev->dev, ++ sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, ++ &channel->lmdesc.base_dma, GFP_KERNEL); + if (!lmdesc) { + dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n"); + return -ENOMEM; +@@ -862,7 +841,26 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + INIT_LIST_HEAD(&channel->ld_free); + INIT_LIST_HEAD(&channel->ld_active); + +- return 0; ++ /* Request the channel interrupt. */ ++ scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index); ++ channel->irq = platform_get_irq_byname(pdev, pdev_irqname); ++ if (channel->irq < 0) ++ return channel->irq; ++ ++ irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", ++ dev_name(dmac->dev), index); ++ if (!irqname) ++ return -ENOMEM; ++ ++ ret = devm_request_threaded_irq(dmac->dev, channel->irq, ++ rz_dmac_irq_handler, ++ rz_dmac_irq_handler_thread, 0, ++ irqname, channel); ++ if (ret) ++ dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", ++ channel->irq, ret); ++ ++ return ret; + } + + static void rz_dmac_put_device(void *_dev) +@@ -932,7 +930,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + const char *irqname = "error"; + struct dma_device *engine; + struct rz_dmac *dmac; +- int channel_num; + int ret; + int irq; + u8 i; +@@ -964,19 +961,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + return PTR_ERR(dmac->ext_base); + } + +- /* Register interrupt handler for error */ +- irq = platform_get_irq_byname(pdev, irqname); +- if (irq < 0) +- return irq; +- +- ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, +- irqname, NULL); +- if (ret) { +- dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", +- irq, ret); +- return ret; +- } +- + /* Initialize the channels. */ + INIT_LIST_HEAD(&dmac->engine.channels); + +@@ -1002,6 +986,21 @@ static int rz_dmac_probe(struct platform_device *pdev) + goto err; + } + ++ /* Register interrupt handler for error */ ++ irq = platform_get_irq_byname(pdev, irqname); ++ if (irq < 0) { ++ ret = irq; ++ goto err; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, ++ irqname, NULL); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", ++ irq, ret); ++ goto err; ++ } ++ + /* Register the DMAC as a DMA provider for DT. */ + ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate, + NULL); +@@ -1040,16 +1039,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + dma_register_err: + of_dma_controller_free(pdev->dev.of_node); + err: +- channel_num = i ? i - 1 : 0; +- for (i = 0; i < channel_num; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } +- + reset_control_assert(dmac->rstc); + err_pm_runtime_put: + pm_runtime_put(&pdev->dev); +@@ -1062,18 +1051,9 @@ static int rz_dmac_probe(struct platform_device *pdev) + static void rz_dmac_remove(struct platform_device *pdev) + { + struct rz_dmac *dmac = platform_get_drvdata(pdev); +- unsigned int i; + + dma_async_device_unregister(&dmac->engine); + of_dma_controller_free(pdev->dev.of_node); +- for (i = 0; i < dmac->n_channels; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } + reset_control_assert(dmac->rstc); + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); +-- +2.53.0 + diff --git a/queue-6.18/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch b/queue-6.18/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch new file mode 100644 index 0000000000..e6f99da1d5 --- /dev/null +++ b/queue-6.18/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch @@ -0,0 +1,43 @@ +From 9e2970db6498c7f9a496b03dda81d97eb94bdb8a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 3 Jun 2026 17:37:49 +0900 +Subject: gpu: host1x: Fix use-after-free in host1x_bo_clear_cached_mappings + +From: Mikko Perttunen + +[ Upstream commit 266cddf7bd0f6c79b6c0633aef742a22bf70265b ] + +__host1x_bo_unpin() drops the last reference to the mapping and frees +it, so we can't dereference mapping afterwards. The cache itself +outlives the mapping, so use the cache local variable instead. + +Reported-by: Dan Carpenter +Closes: https://lore.kernel.org/linux-tegra/ah6ErK6f4kVudVIA@stanley.mountain/T/#u +Signed-off-by: Mikko Perttunen +Signed-off-by: Thierry Reding +Link: https://patch.msgid.link/20260603-host1x-bocache-leak-fix-v1-1-494101dbfd30@nvidia.com +Signed-off-by: Sasha Levin +--- + drivers/gpu/host1x/bus.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c +index 3801eab63282ff..3313ead8bfcc24 100644 +--- a/drivers/gpu/host1x/bus.c ++++ b/drivers/gpu/host1x/bus.c +@@ -1001,10 +1001,10 @@ void host1x_bo_clear_cached_mappings(struct host1x_bo *bo) + if (WARN_ON(!cache)) + continue; + +- mutex_lock(&mapping->cache->lock); ++ mutex_lock(&cache->lock); + WARN_ON(kref_read(&mapping->ref) != 1); + __host1x_bo_unpin(&mapping->ref); +- mutex_unlock(&mapping->cache->lock); ++ mutex_unlock(&cache->lock); + } + } + EXPORT_SYMBOL(host1x_bo_clear_cached_mappings); +-- +2.53.0 + diff --git a/queue-6.18/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch b/queue-6.18/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch new file mode 100644 index 0000000000..325b19b8f4 --- /dev/null +++ b/queue-6.18/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch @@ -0,0 +1,114 @@ +From 2792fa78a13d2f31389f6ad1bf637a689cdfd152 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 8 Apr 2026 09:03:59 -0700 +Subject: Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data() + +From: Seungjin Bae + +[ Upstream commit 875115b82c295277b81b6dfee7debc725f44e854 ] + +The `ims_pcu_process_data()` processes incoming URB data byte by byte. +However, it fails to check if the `read_pos` index exceeds +IMS_PCU_BUF_SIZE. + +If a malicious USB device sends a packet larger than IMS_PCU_BUF_SIZE, +`read_pos` will increment indefinitely. Moreover, since `read_pos` is +located immediately after `read_buf`, the attacker can overwrite +`read_pos` itself to arbitrarily control the index. + +This manipulated `read_pos` is subsequently used in +`ims_pcu_handle_response()` to copy data into `cmd_buf`, leading to a +heap buffer overflow. + +Specifically, an attacker can overwrite the `cmd_done.wait.head` located +at offset 136 relative to `cmd_buf` in the `ims_pcu_handle_response()`. +Consequently, when the driver calls `complete(&pcu->cmd_done)`, it +triggers a control flow hijack by using the manipulated pointer. + +Fix this by adding a bounds check for `read_pos` before writing to +`read_buf`. If the packet is too long, discard it, log a warning, +and reset the parser state. + +Fixes: 628329d524743 ("Input: add IMS Passenger Control Unit driver") +Co-developed-by: Sanghoon Choi +Signed-off-by: Sanghoon Choi +Signed-off-by: Seungjin Bae +Link: https://patch.msgid.link/20251221211442.841549-2-eeodqql09@gmail.com +[dtor: factor out resetting packet state, reset checksum as well] +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 32 ++++++++++++++++++++++++++------ + 1 file changed, 26 insertions(+), 6 deletions(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 94113e14bf5995..7c837094c498ed 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -448,6 +448,14 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + } + } + ++static void ims_pcu_reset_packet(struct ims_pcu *pcu) ++{ ++ pcu->have_stx = true; ++ pcu->have_dle = false; ++ pcu->read_pos = 0; ++ pcu->check_sum = 0; ++} ++ + static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + { + int i; +@@ -460,6 +468,14 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + continue; + + if (pcu->have_dle) { ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->have_dle = false; + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; +@@ -472,10 +488,8 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + dev_warn(pcu->dev, + "Unexpected STX at byte %d, discarding old data\n", + pcu->read_pos); ++ ims_pcu_reset_packet(pcu); + pcu->have_stx = true; +- pcu->have_dle = false; +- pcu->read_pos = 0; +- pcu->check_sum = 0; + break; + + case IMS_PCU_PROTOCOL_DLE: +@@ -495,12 +509,18 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + ims_pcu_handle_response(pcu); + } + +- pcu->have_stx = false; +- pcu->have_dle = false; +- pcu->read_pos = 0; ++ ims_pcu_reset_packet(pcu); + break; + + default: ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + break; +-- +2.53.0 + diff --git a/queue-6.18/input-ims-pcu-fix-logic-error-in-packet-reset.patch b/queue-6.18/input-ims-pcu-fix-logic-error-in-packet-reset.patch new file mode 100644 index 0000000000..8068e8e08e --- /dev/null +++ b/queue-6.18/input-ims-pcu-fix-logic-error-in-packet-reset.patch @@ -0,0 +1,42 @@ +From 28445c99366bbb5e397b081458910faae79be3fb Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 22 May 2026 10:29:41 -0700 +Subject: Input: ims-pcu - fix logic error in packet reset + +From: Dmitry Torokhov + +[ Upstream commit 2c9b85a14abb4811e8d4773ccd13559e59792efb ] + +ims_pcu_reset_packet() incorrectly sets have_stx to true, which implies +that the start-of-packet delimiter has already been received. This +causes the protocol parser to skip waiting for the next STX byte and +potentially process garbage data. + +Correctly set have_stx to false when resetting the packet state. + +Fixes: 875115b82c29 ("Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data()") +Cc: stable@vger.kernel.org +Reported-by: Sashiko bot +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 7c837094c498ed..168662a2cc890c 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -450,7 +450,7 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + + static void ims_pcu_reset_packet(struct ims_pcu *pcu) + { +- pcu->have_stx = true; ++ pcu->have_stx = false; + pcu->have_dle = false; + pcu->read_pos = 0; + pcu->check_sum = 0; +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index 6141a21c43..da7a5c9889 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -15,3 +15,17 @@ kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch kvm-x86-mmu-fix-use-after-free-on-vendor-module-reload.patch +can-bcm-add-locking-when-updating-filter-and-timer-v.patch +can-bcm-fix-can-frame-rx-tx-statistics.patch +can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch +can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch +can-bcm-add-missing-device-refcount-for-can-filter-r.patch +can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch +can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch +can-bcm-track-a-single-source-interface-for-anydev-t.patch +dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch +gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch +crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch +xprtrdma-clear-receive-side-ownership-pointers-on-re.patch +input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch +input-ims-pcu-fix-logic-error-in-packet-reset.patch diff --git a/queue-6.18/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch b/queue-6.18/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch new file mode 100644 index 0000000000..ab135ab0ff --- /dev/null +++ b/queue-6.18/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch @@ -0,0 +1,110 @@ +From fe6260c94808f61ccc7c38b03514cb95178a12b2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 10:14:04 -0400 +Subject: xprtrdma: Clear receive-side ownership pointers on release + +From: Chuck Lever + +[ Upstream commit 2ae8e7afbc63bf84243367f89eb43571f0345a74 ] + +Three small ownership-state cleanups land the transport in a +state that lets future reviewers reason about each pointer +locally rather than tracing the whole reply path: + +rpcrdma_rep_put() clears rep->rr_rqst before the rep enters +rb_free_reps so that no rep on the free list still carries a +stale rqst pointer. rpcrdma_reply_handler() and +rpcrdma_unpin_rqst() are the only sites that set rr_rqst; +rpcrdma_reply_handler() hands the rep through +rpcrdma_rep_put(), and rpcrdma_unpin_rqst() NULLs rr_rqst +directly because its error path abandons the rep for +teardown cleanup rather than returning it to rb_free_reps. + +rpcrdma_reply_put() NULLs req->rl_reply before calling +rpcrdma_rep_put(). The previous order placed the rep on +rb_free_reps while req->rl_reply still pointed at it; the +window was harmless because xprt_rdma_free_slot() holds the +req exclusively across the pair, but closing it makes the +invariant 'rep on rb_free_reps implies no req references it' +strictly checkable. + +rpcrdma_sendctx_unmap() and rpcrdma_sendctx_cancel() clear +req->rl_sendctx after dropping the sendctx pointer in the +sendctx ring. Without this, req->rl_sendctx survives across +Send completion and points at a sendctx that may already have +been reassigned by rpcrdma_sendctx_get_locked() to a different +req. No caller dereferences the stale pointer today -- +rpcrdma_prepare_send_sges() overwrites it before the next +Send -- but a NULL is a more honest representation of 'the +Send is no longer outstanding' and lets the assertion patch +that follows trip on any future regression. + +Signed-off-by: Chuck Lever +Signed-off-by: Anna Schumaker +Signed-off-by: Sasha Levin +--- + net/sunrpc/xprtrdma/rpc_rdma.c | 4 ++++ + net/sunrpc/xprtrdma/verbs.c | 12 ++++++++++-- + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c +index 5d1f96bffd7491..e2694fb03ba1b3 100644 +--- a/net/sunrpc/xprtrdma/rpc_rdma.c ++++ b/net/sunrpc/xprtrdma/rpc_rdma.c +@@ -542,6 +542,7 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + rpcrdma_req_put(req); + } + +@@ -550,8 +551,11 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + */ + static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) + { ++ struct rpcrdma_req *req = sc->sc_req; ++ + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + } + + /* Prepare an SGE for the RPC-over-RDMA transport header. +diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c +index efd91ed249fabe..e2d3cee825cd7f 100644 +--- a/net/sunrpc/xprtrdma/verbs.c ++++ b/net/sunrpc/xprtrdma/verbs.c +@@ -1080,9 +1080,15 @@ static struct rpcrdma_rep *rpcrdma_rep_get_locked(struct rpcrdma_buffer *buf) + * @buf: buffer pool + * @rep: rep to release + * ++ * The rep's transient association with an rpc_rqst, established ++ * by rpcrdma_reply_handler() and torn down here, must not survive ++ * onto rb_free_reps: rpcrdma_post_recvs() pulls reps from the free ++ * list to re-post them, and a non-NULL rr_rqst on a free-listed rep ++ * would imply the rep is still referenced by a req. + */ + void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep) + { ++ rep->rr_rqst = NULL; + llist_add(&rep->rr_node, &buf->rb_free_reps); + } + +@@ -1265,9 +1271,11 @@ rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt) + */ + void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) + { +- if (req->rl_reply) { +- rpcrdma_rep_put(buffers, req->rl_reply); ++ struct rpcrdma_rep *rep = req->rl_reply; ++ ++ if (rep) { + req->rl_reply = NULL; ++ rpcrdma_rep_put(buffers, rep); + } + /* I2: rl_reply NULL after the put closes the + * 'rep on rb_free_reps still referenced by req' window. +-- +2.53.0 + diff --git a/queue-6.6/accel-ivpu-reject-firmware-log-with-size-smaller-tha.patch b/queue-6.6/accel-ivpu-reject-firmware-log-with-size-smaller-tha.patch new file mode 100644 index 0000000000..f4ee537e10 --- /dev/null +++ b/queue-6.6/accel-ivpu-reject-firmware-log-with-size-smaller-tha.patch @@ -0,0 +1,56 @@ +From 050f0e14b594c7cf2bf56f500fbe6199b3ecd0c2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 11:23:40 +0530 +Subject: accel/ivpu: Reject firmware log with size smaller than header + +From: Jhonraushan + +commit ddb44baed257560f192b145ed36cf8c0a412de47 upstream. + +fw_log_from_bo() validates the tracing buffer header_size and that the +log fits within the BO, but never checks that log->size is at least +log->header_size. fw_log_print_buffer() then computes: + + u32 data_size = log->size - log->header_size; + +which underflows to a near-U32_MAX value when firmware reports a log whose +size is smaller than its header. That huge data_size defeats the +log_start/log_end bounds clamps added by commit dd1311bcf0e6 ("accel/ivpu: +Add bounds checks for firmware log indices"), so fw_log_print_lines() reads +far past the small real data region of the BO. A size of 0 also makes +fw_log_from_bo() advance the offset by 0, causing the callers to loop +forever on the same header. + +Reject logs whose size is smaller than the header (which also rejects +size == 0). + +Fixes: d4e4257afa6e ("accel/ivpu: Add firmware tracing support") +Cc: stable@vger.kernel.org +Signed-off-by: Jhonraushan +Reviewed-by: Karol Wachowski +Signed-off-by: Karol Wachowski +Link: https://patch.msgid.link/20260715074206.867712-1-raushan.jhon@gmail.com +Signed-off-by: Raushan Patel +Signed-off-by: Sasha Levin +--- + drivers/accel/ivpu/ivpu_fw_log.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/drivers/accel/ivpu/ivpu_fw_log.c b/drivers/accel/ivpu/ivpu_fw_log.c +index 95065cac9fbdc4..ccad9aa99e5d64 100644 +--- a/drivers/accel/ivpu/ivpu_fw_log.c ++++ b/drivers/accel/ivpu/ivpu_fw_log.c +@@ -43,6 +43,10 @@ static int fw_log_ptr(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *offset, + ivpu_dbg(vdev, FW_BOOT, "Invalid header size 0x%x\n", log->header_size); + return -EINVAL; + } ++ if (log->size < log->header_size) { ++ ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); ++ return -EINVAL; ++ } + if ((char *)log + log->size > (char *)bo->kvaddr + bo->base.size) { + ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); + return -EINVAL; +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-add-locking-when-updating-filter-and-timer-v.patch b/queue-6.6/can-bcm-add-locking-when-updating-filter-and-timer-v.patch new file mode 100644 index 0000000000..6a333ce27d --- /dev/null +++ b/queue-6.6/can-bcm-add-locking-when-updating-filter-and-timer-v.patch @@ -0,0 +1,412 @@ +From 1a4cb9529429962db2c6a510798c3e0b40c8cca8 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:03 +0200 +Subject: can: bcm: add locking when updating filter and timer values + +From: Oliver Hartkopp + +commit 749179c2e25b95d22499ed29096b3e02d6dfd2b4 upstream. + +KCSAN detected a simultaneous access to timer values that can be +overwritten in bcm_rx_setup() when updating timer and filter content +while bcm_rx_handler(), bcm_rx_timeout_handler() or bcm_rx_thr_handler() +run concurrently on incoming CAN traffic. + +Protect the timer (ival1/ival2/kt_ival1/kt_ival2/kt_lastmsg) and filter +(nframes/flags/frames/last_frames) updates in bcm_rx_setup() with a new +per-op bcm_rx_update_lock, taken with the matching scope in the RX +handlers. memcpy_from_msg() is staged into a temporary buffer before the +lock is taken, since it can sleep and must not run under a spinlock. + +hrtimer_cancel() is always called without bcm_rx_update_lock held, since +bcm_rx_timeout_handler()/bcm_rx_thr_handler() take the same lock and a +running callback would otherwise deadlock against the canceller. + +Also close a related race: bcm_rx_setup() cleared the RTR flag in the +stored reply frame's can_id as a separate, unprotected step after the +frame content was already installed, so a concurrent bcm_rx_handler() +could transmit a stale reply with CAN_RTR_FLAG still set. Fold that +normalization into the initial frame preparation instead (on the staged +buffer for updates, directly on op->frames pre-registration for new +ops), so the installed frame is always atomically self-consistent. + +bcm_rx_handler()'s RX_RTR_FRAME check now takes a lock-protected +snapshot of op->flags before deciding whether to call bcm_can_tx(), +but does not hold the lock across that call. + +Also take a lock-protected snapshot of the currframe in bcm_can_tx() +to avoid partly overwrites by content updates in bcm_tx_setup(). +Finally check if a TX_RESET_MULTI_IDX/SETTIMER might have reset +op->currframe between the two locked sections in bcm_can_tx(). + +Omit calling hrtimer_forward() with zero interval in bcm_rx_thr_handler(). +kt_ival2 may have been concurrently cleared by bcm_rx_setup() before it +cancels this timer, so check kt_ival2 inside the bcm_rx_update_lock. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Reported-by: syzbot+75e5e4ae00c3b4bb544e@syzkaller.appspotmail.com +Closes: https://lore.kernel.org/linux-can/6975d5cf.a00a0220.33ccc7.0022.GAE@google.com/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 176 ++++++++++++++++++++++++++++++++++++++------------ + 1 file changed, 133 insertions(+), 43 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 04653df3a173e2..a80a935d6b5009 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -126,6 +126,7 @@ struct bcm_op { + struct sock *sk; + struct net_device *rx_reg_dev; + spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + + struct bcm_sock { +@@ -280,21 +281,27 @@ static int bcm_proc_show(struct seq_file *m, void *v) + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op + */ +-static void bcm_can_tx(struct bcm_op *op) ++static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + { + struct sk_buff *skb; + struct net_device *dev; +- struct canfd_frame *cf; ++ struct canfd_frame cframe; ++ bool cyclic = !cf; ++ unsigned int idx = 0; + int err; + + /* no target device? => exit */ + if (!op->ifindex) + return; + +- /* read currframe under lock protection */ +- spin_lock_bh(&op->bcm_tx_lock); +- cf = op->frames + op->cfsiz * op->currframe; +- spin_unlock_bh(&op->bcm_tx_lock); ++ if (cyclic) { ++ /* read currframe under lock protection */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ idx = op->currframe; ++ memcpy(&cframe, op->frames + op->cfsiz * idx, op->cfsiz); ++ cf = &cframe; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ } + + dev = dev_get_by_index(sock_net(op->sk), op->ifindex); + if (!dev) { +@@ -323,14 +330,20 @@ static void bcm_can_tx(struct bcm_op *op) + if (!err) + op->frames_abs++; + +- op->currframe++; ++ /* only advance the cyclic sequence if nothing reset currframe while ++ * we were sending - a concurrent TX_RESET_MULTI_IDX means this ++ * frame's bookkeeping belongs to a sequence that no longer exists ++ */ ++ if (!cyclic || op->currframe == idx) { ++ op->currframe++; + +- /* reached last frame? */ +- if (op->currframe >= op->nframes) +- op->currframe = 0; ++ /* reached last frame? */ ++ if (op->currframe >= op->nframes) ++ op->currframe = 0; + +- if (op->count > 0) +- op->count--; ++ if (op->count > 0) ++ op->count--; ++ } + + spin_unlock_bh(&op->bcm_tx_lock); + out: +@@ -429,7 +442,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_msg_head msg_head; + + if (op->kt_ival1 && (op->count > 0)) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + if (!op->count && (op->flags & TX_COUNTEVT)) { + + /* create notification to user */ +@@ -446,7 +459,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + } + + } else if (op->kt_ival2) { +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + } + + return bcm_tx_set_expiry(op, &op->timer) ? +@@ -585,6 +598,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + /* if user wants to be informed, when cyclic CAN-Messages come back */ + if ((op->flags & RX_ANNOUNCE_RESUME) && op->last_frames) { + /* clear received CAN frames to indicate 'nothing received' */ +@@ -601,6 +616,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ + bcm_send_to_user(op, &msg_head, NULL, 0); + + return HRTIMER_NORESTART; +@@ -649,15 +666,26 @@ static int bcm_rx_thr_flush(struct bcm_op *op) + static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer); ++ enum hrtimer_restart ret; + +- if (bcm_rx_thr_flush(op)) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ /* kt_ival2 may have been concurrently cleared by bcm_rx_setup() ++ * before it cancels this timer - never forward with a zero ++ * interval in that case. ++ */ ++ if (bcm_rx_thr_flush(op) && op->kt_ival2) { + hrtimer_forward_now(hrtimer, op->kt_ival2); +- return HRTIMER_RESTART; ++ ret = HRTIMER_RESTART; + } else { + /* rearm throttle handling */ + op->kt_lastmsg = 0; +- return HRTIMER_NORESTART; ++ ret = HRTIMER_NORESTART; + } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ return ret; + } + + /* +@@ -667,7 +695,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + { + struct bcm_op *op = (struct bcm_op *)data; + const struct canfd_frame *rxframe = (struct canfd_frame *)skb->data; ++ struct canfd_frame rtrframe; + unsigned int i; ++ bool rtr_frame; + + if (op->can_id != rxframe->can_id) + return; +@@ -691,12 +721,23 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* update statistics */ + op->frames_abs++; + +- if (op->flags & RX_RTR_FRAME) { ++ /* snapshot the flag under lock: op->flags/op->frames may be updated ++ * concurrently by bcm_rx_setup(). ++ */ ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ rtr_frame = op->flags & RX_RTR_FRAME; ++ if (rtr_frame) ++ memcpy(&rtrframe, op->frames, op->cfsiz); ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ +- bcm_can_tx(op); ++ bcm_can_tx(op, &rtrframe); + return; + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe); +@@ -730,6 +771,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + + rx_starttimer: + bcm_rx_starttimer(op); ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + /* +@@ -1073,7 +1116,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + list_add_rcu(&op->list, &bo->tx_ops); + + if (op->flags & TX_ANNOUNCE) +- bcm_can_tx(op); ++ bcm_can_tx(op, NULL); + + if (op->flags & STARTTIMER) + bcm_tx_start_timer(op); +@@ -1087,6 +1130,24 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + ++static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) ++{ ++ /* funny feature in RX(!)_SETUP only for RTR-mode: ++ * copy can_id into frame BUT without RTR-flag to ++ * prevent a full-load-loopback-test ... ;-] ++ * normalize this on the staged buffer, before it is ++ * ever installed into op->frames. ++ */ ++ if (msg_head->flags & RX_RTR_FRAME) { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; ++ } ++} ++ + /* + * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg) + */ +@@ -1121,6 +1182,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->rx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames = NULL; ++ + /* update existing BCM operation */ + + /* +@@ -1132,19 +1195,48 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -E2BIG; + + if (msg_head->nframes) { +- /* update CAN frames content */ +- err = memcpy_from_msg(op->frames, msg, ++ /* get new CAN frames content before locking */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, ++ GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ ++ err = memcpy_from_msg(new_frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + +- /* clear last_frames to indicate 'nothing received' */ +- memset(op->last_frames, 0, msg_head->nframes * op->cfsiz); ++ bcm_rx_setup_rtr_check(msg_head, op, new_frames); + } + ++ spin_lock_bh(&op->bcm_rx_update_lock); + op->nframes = msg_head->nframes; + op->flags = msg_head->flags; + ++ if (msg_head->nframes) { ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, ++ msg_head->nframes * op->cfsiz); ++ ++ /* clear last_frames to indicate 'nothing received' */ ++ memset(op->last_frames, 0, ++ msg_head->nframes * op->cfsiz); ++ } ++ ++ if (msg_head->flags & SETTIMER) { ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ } ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ ++ /* free temporary frames / kfree(NULL) is safe */ ++ kfree(new_frames); ++ + /* Only an update -> do not call can_rx_register() */ + do_rx_register = 0; + +@@ -1155,6 +1247,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return -ENOMEM; + + spin_lock_init(&op->bcm_tx_lock); ++ spin_lock_init(&op->bcm_rx_update_lock); + op->can_id = msg_head->can_id; + op->nframes = msg_head->nframes; + op->cfsiz = CFSIZ(msg_head->flags); +@@ -1196,6 +1289,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + kfree(op); + return err; + } ++ ++ bcm_rx_setup_rtr_check(msg_head, op, op->frames); + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1223,29 +1318,22 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check flags */ + + if (op->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = op->frames; +- + /* no timers in RTR-mode */ + hrtimer_cancel(&op->thrtimer); + hrtimer_cancel(&op->timer); +- +- /* +- * funny feature in RX(!)_SETUP only for RTR-mode: +- * copy can_id into frame BUT without RTR-flag to +- * prevent a full-load-loopback-test ... ;-] +- */ +- if ((op->flags & TX_CP_CAN_ID) || +- (frame0->can_id == op->can_id)) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- + } else { + if (op->flags & SETTIMER) { + +- /* set timer value */ +- op->ival1 = msg_head->ival1; +- op->ival2 = msg_head->ival2; +- op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); +- op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ /* set timers (locked) for newly created op */ ++ if (do_rx_register) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ op->ival1 = msg_head->ival1; ++ op->ival2 = msg_head->ival2; ++ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); ++ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ op->kt_lastmsg = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } + + /* disable an active timer due to zero value? */ + if (!op->kt_ival1) +@@ -1255,9 +1343,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + * In any case cancel the throttle timer, flush + * potentially blocked msgs and reset throttle handling + */ +- op->kt_lastmsg = 0; + hrtimer_cancel(&op->thrtimer); ++ ++ spin_lock_bh(&op->bcm_rx_update_lock); + bcm_rx_thr_flush(op); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + } + + if ((op->flags & STARTTIMER) && op->kt_ival1) +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-add-missing-device-refcount-for-can-filter-r.patch b/queue-6.6/can-bcm-add-missing-device-refcount-for-can-filter-r.patch new file mode 100644 index 0000000000..938a96df4a --- /dev/null +++ b/queue-6.6/can-bcm-add-missing-device-refcount-for-can-filter-r.patch @@ -0,0 +1,121 @@ +From 7744ba91369f4514c97b5f74e7c25878df5f7aa4 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:07 +0200 +Subject: can: bcm: add missing device refcount for CAN filter removal + +From: Oliver Hartkopp + +commit d59948293ea34b6337ce2b5febab8510de70048c upstream. + +sashiko-bot remarked a problem with a concurrent device unregistration +in isotp.c which also is present in the bcm.c code. A former fix for raw.c +commit c275a176e4b6 ("can: raw: add missing refcount for memory leak fix") +introduced a netdevice_tracker which solves the issue for bcm.c too. + +bcm_release(), bcm_delete_rx_op() and bcm_notifier() relied on +dev_get_by_index(ifindex) to re-find the device for an rx_op before +unregistering its filter. If a concurrent NETDEV_UNREGISTER has already +unlisted the device from the ifindex table, that lookup fails and +can_rx_unregister() is silently skipped, leaving a stale CAN filter +pointing at the soon-to-be-freed bcm_op/socket. + +Hold a netdev_hold()/netdev_put() tracked reference on op->rx_reg_dev +from the moment the rx filter is registered in bcm_rx_setup() until it +is unregistered in bcm_rx_unreg(), and use that reference directly in +bcm_release() and bcm_delete_rx_op() instead of re-looking the device +up by ifindex. + +Reported-by: sashiko-bot@kernel.org +Closes: https://sashiko.dev/#/patchset/20260707094716.63578-1-socketcan@hartkopp.net +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 44 ++++++++++++++++++++++++-------------------- + 1 file changed, 24 insertions(+), 20 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index f7733e61690613..b37e494de256b6 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -889,6 +889,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op) + + /* mark as removed subscription */ + op->rx_reg_dev = NULL; ++ dev_put(dev); + } else + printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device " + "mismatch %p %p\n", op->rx_reg_dev, dev); +@@ -919,17 +920,14 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh, + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the dev. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(sock_net(op->sk), +- op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(sock_net(op->sk), NULL, + op->can_id, +@@ -1452,7 +1450,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + bcm_rx_handler, op, + "bcm", sk); + +- op->rx_reg_dev = dev; ++ /* keep a reference so that a later ++ * unregister can safely reach the device even ++ * if a concurrent NETDEV_UNREGISTER has ++ * already unlisted it by ifindex ++ */ ++ if (!err) { ++ op->rx_reg_dev = dev; ++ dev_hold(dev); ++ } + dev_put(dev); + } else { + /* the requested device is gone - do not +@@ -1825,16 +1831,14 @@ static int bcm_release(struct socket *sock) + * Only remove subscriptions that had not + * been removed due to NETDEV_UNREGISTER + * in bcm_notifier() ++ * ++ * op->rx_reg_dev is a tracked reference taken ++ * when the subscription was registered, so it ++ * stays valid here even if a concurrent ++ * NETDEV_UNREGISTER already unlisted the device. + */ +- if (op->rx_reg_dev) { +- struct net_device *dev; +- +- dev = dev_get_by_index(net, op->ifindex); +- if (dev) { +- bcm_rx_unreg(dev, op); +- dev_put(dev); +- } +- } ++ if (op->rx_reg_dev) ++ bcm_rx_unreg(op->rx_reg_dev, op); + } else + can_rx_unregister(net, NULL, op->can_id, + REGMASK(op->can_id), +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch b/queue-6.6/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch new file mode 100644 index 0000000000..fa206cf7d1 --- /dev/null +++ b/queue-6.6/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch @@ -0,0 +1,230 @@ +From 6256dfe38232939dc9af3e43872b460579c434a7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:05 +0200 +Subject: can: bcm: extend bcm_tx_lock usage for data and timer updates + +From: Oliver Hartkopp + +commit 12ce799f7ab1e05bd8fbf79e46f403bfe5597ebc upstream. + +Stage new CAN frame content for an existing tx op into a kmalloc()'d +buffer and validate it there, mirroring the approach already used in +bcm_rx_setup(). Only copy the validated data into op->frames while +holding op->bcm_tx_lock, so bcm_can_tx() and bcm_tx_timeout_handler() +can no longer observe a partially updated or unvalidated frame. + +Add a missing error path for memcpy_from_msg() when copying CAN frame +data from userspace. + +Also move the kt_ival1/kt_ival2/ival1/ival2 updates in bcm_tx_setup() +under op->bcm_tx_lock, and read kt_ival1/kt_ival2/count under the same +lock in bcm_tx_set_expiry() and bcm_tx_timeout_handler(), closing the +torn 64-bit ktime_t read on 32-bit platforms. + +Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 104 ++++++++++++++++++++++++++++++++++++-------------- + 1 file changed, 75 insertions(+), 29 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 980d61d853ee96..ab94caa2d006b0 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -125,7 +125,7 @@ struct bcm_op { + struct canfd_frame last_sframe; + struct sock *sk; + struct net_device *rx_reg_dev; +- spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ ++ spinlock_t bcm_tx_lock; /* protect tx data and timer updates */ + spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ + }; + +@@ -440,12 +440,18 @@ static bool bcm_tx_set_expiry(struct bcm_op *op, struct hrtimer *hrt) + { + ktime_t ival; + ++ spin_lock_bh(&op->bcm_tx_lock); ++ + if (op->kt_ival1 && op->count) + ival = op->kt_ival1; +- else if (op->kt_ival2) ++ else if (op->kt_ival2) { + ival = op->kt_ival2; +- else ++ } else { ++ spin_unlock_bh(&op->bcm_tx_lock); + return false; ++ } ++ ++ spin_unlock_bh(&op->bcm_tx_lock); + + hrtimer_set_expires(hrt, ktime_add(ktime_get(), ival)); + return true; +@@ -462,25 +468,47 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) + { + struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); + struct bcm_msg_head msg_head; ++ bool tx_ival1, tx_ival2; ++ ++ /* snapshot kt_ival1/kt_ival2/count under lock to avoid torn ++ * ktime_t reads racing with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ tx_ival1 = op->kt_ival1 && (op->count > 0); ++ tx_ival2 = !!op->kt_ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (tx_ival1) { ++ u32 flags, count; ++ struct bcm_timeval ival1, ival2; + +- if (op->kt_ival1 && (op->count > 0)) { + bcm_can_tx(op, NULL); +- if (!op->count && (op->flags & TX_COUNTEVT)) { + ++ /* snapshot variables under lock to avoid torn reads racing ++ * with concurrent bcm_tx_setup() updates ++ */ ++ spin_lock_bh(&op->bcm_tx_lock); ++ flags = op->flags; ++ count = op->count; ++ ival1 = op->ival1; ++ ival2 = op->ival2; ++ spin_unlock_bh(&op->bcm_tx_lock); ++ ++ if (!count && (flags & TX_COUNTEVT)) { + /* create notification to user */ + memset(&msg_head, 0, sizeof(msg_head)); + msg_head.opcode = TX_EXPIRED; +- msg_head.flags = op->flags; +- msg_head.count = op->count; +- msg_head.ival1 = op->ival1; +- msg_head.ival2 = op->ival2; ++ msg_head.flags = flags; ++ msg_head.count = count; ++ msg_head.ival1 = ival1; ++ msg_head.ival2 = ival2; + msg_head.can_id = op->can_id; + msg_head.nframes = 0; + + bcm_send_to_user(op, &msg_head, NULL, 0); + } + +- } else if (op->kt_ival2) { ++ } else if (tx_ival2) { + bcm_can_tx(op, NULL); + } + +@@ -988,6 +1016,8 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* check the given can_id */ + op = bcm_find_op(&bo->tx_ops, msg_head, ifindex); + if (op) { ++ void *new_frames; ++ + /* update existing BCM operation */ + + /* +@@ -998,11 +1028,23 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes > op->nframes) + return -E2BIG; + +- /* update CAN frames content */ ++ /* get new CAN frames content into a staging buffer before ++ * locking: validate and normalize the frames there so that ++ * bcm_can_tx() / bcm_tx_timeout_handler() never observe a ++ * partially updated or unvalidated frame in op->frames ++ */ ++ new_frames = kmalloc(msg_head->nframes * op->cfsiz, GFP_KERNEL); ++ if (!new_frames) ++ return -ENOMEM; ++ + for (i = 0; i < msg_head->nframes; i++) { + +- cf = op->frames + op->cfsiz * i; ++ cf = new_frames + op->cfsiz * i; + err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + + if (op->flags & CAN_FD_FRAME) { + if (cf->len > 64) +@@ -1012,36 +1054,38 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -EINVAL; + } + +- if (err < 0) ++ if (err < 0) { ++ kfree(new_frames); + return err; ++ } + + if (msg_head->flags & TX_CP_CAN_ID) { + /* copy can_id into frame */ + cf->can_id = msg_head->can_id; + } + } ++ ++ spin_lock_bh(&op->bcm_tx_lock); ++ ++ /* update CAN frames content */ ++ memcpy(op->frames, new_frames, msg_head->nframes * op->cfsiz); ++ + op->flags = msg_head->flags; + +- /* only lock for unlikely count/nframes/currframe changes */ + if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX || +- op->flags & SETTIMER) { +- +- spin_lock_bh(&op->bcm_tx_lock); ++ op->flags & TX_RESET_MULTI_IDX) { ++ /* potentially update changed nframes */ ++ op->nframes = msg_head->nframes; ++ /* restart multiple frame transmission */ ++ op->currframe = 0; ++ } + +- if (op->nframes != msg_head->nframes || +- op->flags & TX_RESET_MULTI_IDX) { +- /* potentially update changed nframes */ +- op->nframes = msg_head->nframes; +- /* restart multiple frame transmission */ +- op->currframe = 0; +- } ++ if (op->flags & SETTIMER) ++ op->count = msg_head->count; + +- if (op->flags & SETTIMER) +- op->count = msg_head->count; ++ spin_unlock_bh(&op->bcm_tx_lock); + +- spin_unlock_bh(&op->bcm_tx_lock); +- } ++ kfree(new_frames); + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1118,10 +1162,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + if (op->flags & SETTIMER) { + /* set timer values */ ++ spin_lock_bh(&op->bcm_tx_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); ++ spin_unlock_bh(&op->bcm_tx_lock); + + /* disable an active timer due to zero values? */ + if (!op->kt_ival1 && !op->kt_ival2) +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-fix-can-frame-rx-tx-statistics.patch b/queue-6.6/can-bcm-fix-can-frame-rx-tx-statistics.patch new file mode 100644 index 0000000000..710833be15 --- /dev/null +++ b/queue-6.6/can-bcm-fix-can-frame-rx-tx-statistics.patch @@ -0,0 +1,191 @@ +From c250391e31d2d7ed7da81326c63ac9b9e9ab5757 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:04 +0200 +Subject: can: bcm: fix CAN frame rx/tx statistics + +From: Oliver Hartkopp + +commit e6c24ba95fc3f1b5e1dcd28b1c6e59ef61a9daa5 upstream. + +KCSAN detected a data race within the bcm_rx_handler() when two CAN frames +have been simultaneously received and processed in a single rx op by two +different CPUs. + +Use atomic operations with (signed) long data types to access the +statistics in the hot path to fix the KCSAN complaint. + +Additionally simplify the update and check of statistics overflow by +using the atomic operations in separate bcm_update_[rx|tx]_stats() +functions. The rx variant runs under bcm_rx_update_lock to prevent +races when resetting the two rx counters; the tx variant runs under +bcm_tx_lock and only needs to guard its own counter's overflow. + +As the rx path resets its values already at LONG_MAX / 100, there is +no conflict between the two locking domains (bcm_rx_update_lock vs. +bcm_tx_lock) even for ops that use both paths. + +The rx statistics update and the frames_filtered update in +bcm_rx_changed() were previously performed in two separate +bcm_rx_update_lock sections. For an rx op subscribed on all interfaces +(ifindex == 0), bcm_rx_handler() can run concurrently on different +CPUs, so a counter reset by one CPU between these two sections could +leave frames_filtered larger than frames_abs on another CPU, producing +a bogus (even negative) reduction percentage in procfs. Update the +statistics in the same critical section as bcm_rx_changed() to close +this gap, which also removes the now unneeded extra lock/unlock pair +around the traffic_flags calculation. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 67 ++++++++++++++++++++++++++++++++++----------------- + 1 file changed, 45 insertions(+), 22 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index a80a935d6b5009..980d61d853ee96 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -109,7 +109,7 @@ struct bcm_op { + int ifindex; + canid_t can_id; + u32 flags; +- unsigned long frames_abs, frames_filtered; ++ atomic_long_t frames_abs, frames_filtered; + struct bcm_timeval ival1, ival2; + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; +@@ -216,10 +216,13 @@ static int bcm_proc_show(struct seq_file *m, void *v) + + list_for_each_entry_rcu(op, &bo->rx_ops, list) { + +- unsigned long reduction; ++ long reduction, frames_filtered, frames_abs; ++ ++ frames_filtered = atomic_long_read(&op->frames_filtered); ++ frames_abs = atomic_long_read(&op->frames_abs); + + /* print only active entries & prevent division by zero */ +- if (!op->frames_abs) ++ if (!frames_abs) + continue; + + seq_printf(m, "rx_op: %03X %-5s ", op->can_id, +@@ -241,9 +244,9 @@ static int bcm_proc_show(struct seq_file *m, void *v) + (long long)ktime_to_us(op->kt_ival2)); + + seq_printf(m, "# recv %ld (%ld) => reduction: ", +- op->frames_filtered, op->frames_abs); ++ frames_filtered, frames_abs); + +- reduction = 100 - (op->frames_filtered * 100) / op->frames_abs; ++ reduction = 100 - (frames_filtered * 100) / frames_abs; + + seq_printf(m, "%s%ld%%\n", + (reduction == 100) ? "near " : "", reduction); +@@ -267,7 +270,8 @@ static int bcm_proc_show(struct seq_file *m, void *v) + seq_printf(m, "t2=%lld ", + (long long)ktime_to_us(op->kt_ival2)); + +- seq_printf(m, "# sent %ld\n", op->frames_abs); ++ seq_printf(m, "# sent %ld\n", ++ atomic_long_read(&op->frames_abs)); + } + seq_putc(m, '\n'); + +@@ -277,6 +281,24 @@ static int bcm_proc_show(struct seq_file *m, void *v) + } + #endif /* CONFIG_PROC_FS */ + ++static void bcm_update_rx_stats(struct bcm_op *op) ++{ ++ /* prevent overflow of the reduction% calculation in bcm_proc_show() */ ++ if (atomic_long_inc_return(&op->frames_abs) > LONG_MAX / 100) { ++ atomic_long_set(&op->frames_filtered, 0); ++ atomic_long_set(&op->frames_abs, 0); ++ } ++} ++ ++static void bcm_update_tx_stats(struct bcm_op *op) ++{ ++ /* tx_op has no reduction% calculation - use the full range and ++ * just keep the displayed counter non-negative on overflow ++ */ ++ if (atomic_long_inc_return(&op->frames_abs) == LONG_MAX) ++ atomic_long_set(&op->frames_abs, 0); ++} ++ + /* + * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface + * of the given bcm tx op +@@ -328,7 +350,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) + spin_lock_bh(&op->bcm_tx_lock); + + if (!err) +- op->frames_abs++; ++ bcm_update_tx_stats(op); + + /* only advance the cyclic sequence if nothing reset currframe while + * we were sending - a concurrent TX_RESET_MULTI_IDX means this +@@ -473,12 +495,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data) + { + struct bcm_msg_head head; + +- /* update statistics */ +- op->frames_filtered++; +- +- /* prevent statistics overflow */ +- if (op->frames_filtered > ULONG_MAX/100) +- op->frames_filtered = op->frames_abs = 0; ++ /* update statistics (frames_filtered <= frames_abs) */ ++ if (atomic_long_read(&op->frames_abs)) ++ atomic_long_inc(&op->frames_filtered); + + /* this element is not throttled anymore */ + data->flags &= (BCM_CAN_FLAGS_MASK|RX_RECV); +@@ -718,25 +737,29 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + op->rx_stamp = skb->tstamp; + /* save originator for recvfrom() */ + op->rx_ifindex = skb->dev->ifindex; +- /* update statistics */ +- op->frames_abs++; + +- /* snapshot the flag under lock: op->flags/op->frames may be updated +- * concurrently by bcm_rx_setup(). +- */ ++ /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); ++ + rtr_frame = op->flags & RX_RTR_FRAME; +- if (rtr_frame) ++ if (rtr_frame) { ++ bcm_update_rx_stats(op); ++ /* snapshot RTR content under lock */ + memcpy(&rtrframe, op->frames, op->cfsiz); +- spin_unlock_bh(&op->bcm_rx_update_lock); ++ spin_unlock_bh(&op->bcm_rx_update_lock); + +- if (rtr_frame) { + /* send reply for RTR-request (placed in op->frames[0]) */ + bcm_can_tx(op, &rtrframe); + return; + } + +- spin_lock_bh(&op->bcm_rx_update_lock); ++ /* update statistics in the same critical section as bcm_rx_changed() ++ * below: frames_filtered must never be checked/incremented against a ++ * frames_abs snapshot from a concurrent bcm_rx_handler() call on ++ * another CPU for the same (wildcard) op, or frames_filtered can end ++ * up larger than frames_abs. ++ */ ++ bcm_update_rx_stats(op); + + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch b/queue-6.6/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch new file mode 100644 index 0000000000..caed33cc43 --- /dev/null +++ b/queue-6.6/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch @@ -0,0 +1,74 @@ +From 46eded3636da18fe429e2da6541e42b677c0c653 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:09 +0200 +Subject: can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler() + +From: Oliver Hartkopp + +commit 58fd6cbc8541216af1d7ed272ea7ac2b66d50fd8 upstream. + +For an rx op subscribed on all interfaces (ifindex == 0), the same op +is registered once in the shared per-netns wildcard filter list, so +bcm_rx_handler() can run concurrently on different CPUs for frames +arriving on different net devices. + +op->rx_stamp and op->rx_ifindex were written before bcm_rx_update_lock was +taken, allowing concurrent writers to race each other - including a torn +store of the 64-bit rx_stamp on 32-bit platforms. + +Beyond a torn store bcm_send_to_user() must report the timestamp/ifindex +of the very same frame whose content it is delivering. So the assignment +is placed in the same unbroken bcm_rx_update_lock section as the content +comparison. + +As a side effect, the RTR-request frame feature (which never reach +bcm_send_to_user()) no longer updates rx_stamp/rx_ifindex, since only +the notification path needs them. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260707145135.5BC831F00A3A@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 80d065b5ebe467..0869630a0dc06b 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -761,11 +761,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + /* disable timeout */ + hrtimer_cancel(&op->timer); + +- /* save rx timestamp */ +- op->rx_stamp = skb->tstamp; +- /* save originator for recvfrom() */ +- op->rx_ifindex = skb->dev->ifindex; +- + /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ + spin_lock_bh(&op->bcm_rx_update_lock); + +@@ -789,6 +784,14 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + */ + bcm_update_rx_stats(op); + ++ /* save rx timestamp and originator for recvfrom() under lock. ++ * For an op subscribed on all interfaces (ifindex == 0) ++ * bcm_rx_handler() can run concurrently on different CPUs so ++ * the CAN content and the meta data must be bundled correctly. ++ */ ++ op->rx_stamp = skb->tstamp; ++ op->rx_ifindex = skb->dev->ifindex; ++ + if (op->flags & RX_FILTER_ID) { + /* the easiest case */ + bcm_rx_update_and_send(op, op->last_frames, rxframe); +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch b/queue-6.6/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch new file mode 100644 index 0000000000..9234b18281 --- /dev/null +++ b/queue-6.6/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch @@ -0,0 +1,164 @@ +From 042f470eec4e550871283bdd29620ea92ef1450e Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:08 +0200 +Subject: can: bcm: fix stale rx/tx ops after device removal + +From: Oliver Hartkopp + +commit 3b762c0d950383ab7a002686c9136b9aa55d2d70 upstream. + +RX: an RX_SETUP update(!) for an existing op skipped can_rx_register() +unconditionally, even when a concurrent NETDEV_UNREGISTER had already +torn down its registration (op->rx_reg_dev == NULL). This silently +did not re-enable frame delivery for that updated filter. bcm_rx_setup() +now re-registers in that case, while leaving rx_ops with ifindex = 0 +(all CAN devices) which never carry a tracked rx_reg_dev registered as-is. + +TX: bcm_notify() only handled bo->rx_ops on NETDEV_UNREGISTER, leaving +tx_ops with an active cyclic transmission re-arming its hrtimer +indefinitely to execute bcm_tx_timeout_handler(). Cancelling the hrtimer +prevents the runaway timer and any injection into a later reused ifindex, +since nothing else calls bcm_can_tx() for the op until an explicit +TX_SETUP update re-arms it. + +Unlike bcm_rx_unreg(), which clears the tracked rx_reg_dev for rx_ops, +the ifindex is intentionally left unchanged for tx_ops. bcm_tx_setup() +always rejects ifindex 0, so clearing it would strand the op: neither a +later TX_SETUP (bcm_find_op()) nor TX_DELETE (bcm_delete_tx_op()) could +ever find it again, since both require an exact ifindex match. + +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260708094536.DDF821F00A3A@smtp.kernel.org/ +Closes: https://lore.kernel.org/linux-can/20260708154039.347ED1F000E9@smtp.kernel.org/ +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 54 +++++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 44 insertions(+), 10 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index b37e494de256b6..80d065b5ebe467 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1239,6 +1239,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + struct bcm_sock *bo = bcm_sk(sk); + struct bcm_op *op; + int do_rx_register; ++ int new_op = 0; + int err = 0; + + if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) { +@@ -1323,8 +1324,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + /* free temporary frames / kfree(NULL) is safe */ + kfree(new_frames); + +- /* Only an update -> do not call can_rx_register() */ +- do_rx_register = 0; ++ /* Don't register a new CAN filter for the rx_op update unless ++ * a concurrent NETDEV_UNREGISTER notifier already tore down ++ * the previous registration. In this case the receiver needs ++ * to be re-registered here so that this update doesn't ++ * silently stop delivering frames for the given ifindex. ++ * Ops with ifindex = 0 (all CAN interfaces) never carry a ++ * tracked rx_reg_dev and stay registered as-is. ++ */ ++ do_rx_register = (ifindex && !op->rx_reg_dev) ? 1 : 0; + + } else { + /* insert new BCM operation for the given can_id */ +@@ -1394,6 +1402,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + + /* call can_rx_register() */ + do_rx_register = 1; ++ new_op = 1; + + } /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */ + +@@ -1407,7 +1416,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (op->flags & SETTIMER) { + + /* set timers (locked) for newly created op */ +- if (do_rx_register) { ++ if (new_op) { + spin_lock_bh(&op->bcm_rx_update_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; +@@ -1437,7 +1446,10 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + HRTIMER_MODE_REL_SOFT); + } + +- /* now we can register for can_ids, if we added a new bcm_op */ ++ /* now we can register for can_ids, if we added a new bcm_op ++ * or need to re-register after a NETDEV_UNREGISTER tore down ++ * the previous registration of an existing op ++ */ + if (do_rx_register) { + if (ifindex) { + struct net_device *dev; +@@ -1467,18 +1479,32 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + err = -ENODEV; + } + +- } else ++ } else { + err = can_rx_register(sock_net(sk), NULL, op->can_id, + REGMASK(op->can_id), + bcm_rx_handler, op, "bcm", sk); ++ } ++ + if (err) { +- /* this bcm rx op is broken -> remove it */ +- bcm_remove_op(op); ++ /* newly created bcm rx op is broken -> remove it */ ++ if (new_op) { ++ bcm_remove_op(op); ++ return err; ++ } ++ ++ /* an existing op just stays unregistered. ++ * Cancel op->timer and (defensively) op->thrtimer. ++ * Other settings can't be reached until the next ++ * successful RX_SETUP. ++ */ ++ hrtimer_cancel(&op->timer); ++ hrtimer_cancel(&op->thrtimer); + return err; + } + +- /* add this bcm_op to the list of the rx_ops */ +- list_add_rcu(&op->list, &bo->rx_ops); ++ /* add a new bcm_op to the list of the rx_ops */ ++ if (new_op) ++ list_add_rcu(&op->list, &bo->rx_ops); + } + + return msg_head->nframes * op->cfsiz + MHSIZ; +@@ -1694,11 +1720,19 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + case NETDEV_UNREGISTER: + lock_sock(sk); + +- /* remove device specific receive entries */ ++ /* rx_ops: remove device specific receive entries */ + list_for_each_entry(op, &bo->rx_ops, list) + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* tx_ops: stop device specific cyclic transmissions on the ++ * vanishing ifindex. Cancelling the timer is enough to stop ++ * cyclic bcm_can_tx() calls as there is no re-arming. ++ */ ++ list_for_each_entry(op, &bo->tx_ops, list) ++ if (op->ifindex == dev->ifindex) ++ hrtimer_cancel(&op->timer); ++ + /* remove device reference, if this is our bound device */ + if (bo->bound && bo->ifindex == dev->ifindex) { + #if IS_ENABLED(CONFIG_PROC_FS) +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-track-a-single-source-interface-for-anydev-t.patch b/queue-6.6/can-bcm-track-a-single-source-interface-for-anydev-t.patch new file mode 100644 index 0000000000..9dc8d5f33f --- /dev/null +++ b/queue-6.6/can-bcm-track-a-single-source-interface-for-anydev-t.patch @@ -0,0 +1,143 @@ +From c267bd5d307ef2b926f31f48f3656840ad916b8f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:10 +0200 +Subject: can: bcm: track a single source interface for ANYDEV timeout/throttle + ops + +From: Oliver Hartkopp + +commit 2f5976f54a04e9f18b25283036ac3136be453b17 upstream. + +An ANYDEV rx op (ifindex == 0) with an active RX timeout and/or +throttle timer has no defined semantics when matching frames arrive +from several interfaces: bcm_rx_handler() can run concurrently for +the same op on different CPUs, racing hrtimer_cancel()/ +bcm_rx_starttimer() against bcm_rx_timeout_handler() and causing +spurious RX_TIMEOUT notifications and last_frames corruption. The +same concurrency lets throttled multiplex frames from different +interfaces clobber the single rx_ifindex/rx_stamp fields shared by +the op. + +Add op->if_detected to track the first interface that delivers a +matching frame while a timeout/throttle timer is configured, and +reject frames from any other interface for that op. The claim is +decided in bcm_rx_handler() before hrtimer_cancel() touches +op->timer, so a rejected frame can never disturb the claimed +interface's watchdog. RTR-mode ops are excluded via RX_RTR_FRAME, +independent of kt_ival1/kt_ival2, since those may briefly hold a +stale value from an earlier non-RTR configuration. + +The claim is released in bcm_notify() on NETDEV_UNREGISTER and in +bcm_rx_setup() when SETTIMER reconfigures the timer values. + +A (re-)claim is only possible on CAN devices in NETREG_REGISTERED +dev->reg_state to cover the release in bcm_notify() where reg_state +becomes NETREG_UNREGISTERING until synchronize_net(). + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260709105031.1A39C1F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 44 insertions(+), 5 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index 0869630a0dc06b..fcd923c223f4ba 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -114,6 +114,7 @@ struct bcm_op { + struct hrtimer timer, thrtimer; + ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; + int rx_ifindex; ++ int if_detected; /* first received ifindex in ANYDEV rx_op mode */ + int cfsiz; + u32 count; + u32 nframes; +@@ -758,6 +759,33 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + return; + } + ++ /* An ANYDEV op with an active RX timeout and/or throttle timer ++ * tracks a single source interface: claim the first interface that ++ * delivers a matching frame and reject frames from any other one, ++ * before hrtimer_cancel() below can touch op->timer - this avoids ++ * racing bcm_rx_timeout_handler() across concurrent interfaces. ++ * RX_RTR_FRAME ops are excluded, as kt_ival1/kt_ival2 may briefly ++ * hold a stale value from an earlier non-RTR configuration. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ ++ if (!(op->flags & RX_RTR_FRAME) && ++ (op->kt_ival1 || op->kt_ival2)) { ++ /* don't claim to vanishing interface */ ++ if (!op->if_detected && ++ skb->dev->reg_state == NETREG_REGISTERED) ++ op->if_detected = skb->dev->ifindex; ++ ++ if (op->if_detected != skb->dev->ifindex) { ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ return; ++ } ++ } ++ ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ + /* disable timeout */ + hrtimer_cancel(&op->timer); + +@@ -784,10 +812,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) + */ + bcm_update_rx_stats(op); + +- /* save rx timestamp and originator for recvfrom() under lock. +- * For an op subscribed on all interfaces (ifindex == 0) +- * bcm_rx_handler() can run concurrently on different CPUs so +- * the CAN content and the meta data must be bundled correctly. ++ /* save rx timestamp and originator for recvfrom() under lock: an ++ * ANYDEV op without an active timer can still run concurrently on ++ * different CPUs, so content and meta data must be bundled here. + */ + op->rx_stamp = skb->tstamp; + op->rx_ifindex = skb->dev->ifindex; +@@ -1321,6 +1348,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + op->kt_lastmsg = 0; ++ op->if_detected = 0; /* reclaim ifindex in ANYDEV mode */ + } + spin_unlock_bh(&op->bcm_rx_update_lock); + +@@ -1724,10 +1752,21 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, + lock_sock(sk); + + /* rx_ops: remove device specific receive entries */ +- list_for_each_entry(op, &bo->rx_ops, list) ++ list_for_each_entry(op, &bo->rx_ops, list) { + if (op->rx_reg_dev == dev) + bcm_rx_unreg(dev, op); + ++ /* release an ANYDEV op's claim (see bcm_rx_handler()) ++ * on this now confirmed-gone interface. ++ */ ++ if (!op->ifindex) { ++ spin_lock_bh(&op->bcm_rx_update_lock); ++ if (op->if_detected == dev->ifindex) ++ op->if_detected = 0; ++ spin_unlock_bh(&op->bcm_rx_update_lock); ++ } ++ } ++ + /* tx_ops: stop device specific cyclic transmissions on the + * vanishing ifindex. Cancelling the timer is enough to stop + * cyclic bcm_can_tx() calls as there is no re-arming. +-- +2.53.0 + diff --git a/queue-6.6/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch b/queue-6.6/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch new file mode 100644 index 0000000000..563f88fff9 --- /dev/null +++ b/queue-6.6/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch @@ -0,0 +1,128 @@ +From 7886e5087b981f01f85e5033bcb8cecea03fc9f7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:24:06 +0200 +Subject: can: bcm: validate frame length in bcm_rx_setup() for RTR replies + +From: Oliver Hartkopp + +commit 62ec41f364648be79d54d94d0d240ee326948afd upstream. + +bcm_tx_setup() validates cf->len against the CAN/CAN FD DLC limits +before installing frames for TX_SETUP, but bcm_rx_setup() never did +the same for the RTR-reply frame configured via RX_SETUP with +RX_RTR_FRAME. + +Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++---------------- + 1 file changed, 41 insertions(+), 18 deletions(-) + +diff --git a/net/can/bcm.c b/net/can/bcm.c +index ab94caa2d006b0..f7733e61690613 100644 +--- a/net/can/bcm.c ++++ b/net/can/bcm.c +@@ -1199,22 +1199,37 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +-static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, +- struct bcm_op *op, void *new_frames) ++static int bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, ++ struct bcm_op *op, void *new_frames) + { ++ struct canfd_frame *frame0 = new_frames; ++ ++ if (!(msg_head->flags & RX_RTR_FRAME)) ++ return 0; ++ ++ /* this frame is sent out as-is by bcm_can_tx() whenever a matching ++ * remote request is received, so validate its length the same way ++ * bcm_tx_setup() validates TX_SETUP frames before installing it ++ */ ++ if (msg_head->flags & CAN_FD_FRAME) { ++ if (frame0->len > 64) ++ return -EINVAL; ++ } else { ++ if (frame0->len > 8) ++ return -EINVAL; ++ } ++ + /* funny feature in RX(!)_SETUP only for RTR-mode: + * copy can_id into frame BUT without RTR-flag to + * prevent a full-load-loopback-test ... ;-] + * normalize this on the staged buffer, before it is + * ever installed into op->frames. + */ +- if (msg_head->flags & RX_RTR_FRAME) { +- struct canfd_frame *frame0 = new_frames; ++ if ((msg_head->flags & TX_CP_CAN_ID) || ++ frame0->can_id == op->can_id) ++ frame0->can_id = op->can_id & ~CAN_RTR_FLAG; + +- if ((msg_head->flags & TX_CP_CAN_ID) || +- frame0->can_id == op->can_id) +- frame0->can_id = op->can_id & ~CAN_RTR_FLAG; +- } ++ return 0; + } + + /* +@@ -1277,7 +1292,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + return err; + } + +- bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, new_frames); ++ if (err < 0) { ++ kfree(new_frames); ++ return err; ++ } + } + + spin_lock_bh(&op->bcm_rx_update_lock); +@@ -1350,16 +1369,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + if (msg_head->nframes) { + err = memcpy_from_msg(op->frames, msg, + msg_head->nframes * op->cfsiz); +- if (err < 0) { +- if (op->frames != &op->sframe) +- kfree(op->frames); +- if (op->last_frames != &op->last_sframe) +- kfree(op->last_frames); +- kfree(op); +- return err; +- } ++ if (err < 0) ++ goto free_op; + +- bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ err = bcm_rx_setup_rtr_check(msg_head, op, op->frames); ++ if (err < 0) ++ goto free_op; + } + + /* bcm_can_tx / bcm_tx_timeout_handler needs this */ +@@ -1461,6 +1476,14 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, + } + + return msg_head->nframes * op->cfsiz + MHSIZ; ++ ++free_op: ++ if (op->frames != &op->sframe) ++ kfree(op->frames); ++ if (op->last_frames != &op->last_sframe) ++ kfree(op->last_frames); ++ kfree(op); ++ return err; + } + + /* +-- +2.53.0 + diff --git a/queue-6.6/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch b/queue-6.6/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch new file mode 100644 index 0000000000..4a10510d43 --- /dev/null +++ b/queue-6.6/can-isotp-fix-use-after-free-race-with-concurrent-ne.patch @@ -0,0 +1,238 @@ +From 29d63b803007ca0a6595b342ceb5b87683e67e57 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:34:08 +0200 +Subject: can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER + +From: Oliver Hartkopp + +commit 20bab8b88baac140ca3701116e1d486c7f51e311 upstream. + +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|rx].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 +Closes: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-2-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 88 +++++++++++++++++++++++++++++++++---------------- + 1 file changed, 59 insertions(+), 29 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index 80adf7366e63a8..b906fcdb386cd4 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -150,6 +150,7 @@ struct isotp_sock { + struct sock sk; + int bound; + int ifindex; ++ struct net_device *dev; + canid_t txid; + canid_t rxid; + ktime_t tx_gap; +@@ -962,6 +963,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + 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); +@@ -1197,28 +1206,30 @@ static int isotp_release(struct socket *sock) + 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); ++ dev_put(so->dev); + } + ++ 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 +@@ -1231,9 +1242,6 @@ static int isotp_release(struct socket *sock) + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->rxtimer); + +- so->ifindex = 0; +- so->bound = 0; +- + sock_orphan(sk); + sock->sk = NULL; + +@@ -1287,6 +1295,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + if (!addr->can_ifindex) + return -ENODEV; + ++ rtnl_lock(); + lock_sock(sk); + + if (so->bound) { +@@ -1294,6 +1303,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + ++ /* A transmission or reception that outlived a previous binding ++ * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo ++ * and RX 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 || so->rx.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; +@@ -1306,14 +1326,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + goto out; + } + if (dev->type != ARPHRD_CAN) { +- dev_put(dev); + err = -ENODEV; +- goto out; ++ goto out_put_dev; + } +- if (dev->mtu < so->ll.mtu) { +- dev_put(dev); ++ if (READ_ONCE(dev->mtu) < so->ll.mtu) { + err = -EINVAL; +- goto out; ++ goto out_put_dev; + } + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; +@@ -1331,16 +1349,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + 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; ++ dev_hold(so->dev); ++ ++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; +@@ -1543,7 +1570,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + if (!net_eq(dev_net(dev), sock_net(sk))) + return; + +- if (so->ifindex != dev->ifindex) ++ if (so->dev != dev) + return; + + switch (msg) { +@@ -1559,10 +1586,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + can_rx_unregister(dev_net(dev), dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); ++ dev_put(so->dev); + } + + so->ifindex = 0; + so->bound = 0; ++ so->dev = NULL; + release_sock(sk); + + sk->sk_err = ENODEV; +@@ -1622,6 +1651,7 @@ static int isotp_init(struct sock *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; +-- +2.53.0 + diff --git a/queue-6.6/can-isotp-serialize-tx-state-transitions-under-so-rx.patch b/queue-6.6/can-isotp-serialize-tx-state-transitions-under-so-rx.patch new file mode 100644 index 0000000000..cefbf76b52 --- /dev/null +++ b/queue-6.6/can-isotp-serialize-tx-state-transitions-under-so-rx.patch @@ -0,0 +1,426 @@ +From 9fe755b867c64514a728d96242af2ffd28d59737 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:34:09 +0200 +Subject: can: isotp: serialize TX state transitions under so->rx_lock + +From: Oliver Hartkopp + +commit cf070fe33bfbd1a4c21236078fadb35dd223a157 upstream. + +The TX state machine (so->tx.state) is driven from three contexts: +sendmsg() claiming and progressing a transfer, the RX path consuming +Flow Control/echo frames, and two hrtimers timing out a stalled +transfer. Mixing a lock-free cmpxchg() claim in sendmsg() with +hrtimer_cancel() calls made under so->rx_lock elsewhere left windows +where a frame or timer callback could act on a state that had already +moved on, corrupting an unrelated transfer. + +so->rx_lock now covers the full lifecycle of a TX claim: sendmsg() +takes it to check so->tx.state is ISOTP_IDLE, switch it to +ISOTP_SENDING, bump so->tx_gen and drain the previous transfer's +timers - all as one critical section. isotp_rcv_fc()/isotp_rcv_cf() +already run under this lock via isotp_rcv(), and isotp_rcv_echo() now +takes it itself, so none of them can ever observe a transfer mid-claim. +This also means a transfer can no longer be handed to sendmsg()'s +cleanup paths (signal or send error) while another thread is +concurrently claiming or finishing it, so those paths can cancel +timers and reset the state unconditionally. + +isotp_release() claims the socket the same way, so a racing sendmsg() +sees a consistent ISOTP_SHUTDOWN and skips arming its timer or sending. + +Only the hrtimer callbacks stay outside so->rx_lock, since they run +under so->rx_lock's cancellation elsewhere and taking it themselves +would deadlock. so->tx_gen lets them recognize whether the transfer +they timed out is still the one currently active, so they don't +report an error against a transfer that has since completed or been +superseded. + +Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") +Reported-by: sashiko-bot@kernel.org +Closes: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260712-isotp-fixes-v10-3-793a1b1ce17f@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + net/can/isotp.c | 192 ++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 154 insertions(+), 38 deletions(-) + +diff --git a/net/can/isotp.c b/net/can/isotp.c +index b906fcdb386cd4..efc5eeac7c8861 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -155,7 +155,7 @@ struct isotp_sock { + canid_t rxid; + ktime_t tx_gap; + ktime_t lastrxcf_tstamp; +- struct hrtimer rxtimer, txtimer, txfrtimer; ++ struct hrtimer rxtimer, txtimer, txfrtimer, echotimer; + struct can_isotp_options opt; + struct can_isotp_fc_options rxfc, txfc; + struct can_isotp_ll_options ll; +@@ -163,6 +163,7 @@ struct isotp_sock { + u32 force_tx_stmin; + u32 force_rx_stmin; + u32 cfecho; /* consecutive frame echo tag */ ++ u32 tx_gen; /* generation, bumped per new tx transfer */ + struct tpcon rx, tx; + struct list_head notifier; + wait_queue_head_t wait; +@@ -369,6 +370,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + + hrtimer_cancel(&so->txtimer); + ++ /* isotp_tx_timeout() may have given up on this job while ++ * hrtimer_cancel() above waited for it to finish; so->rx_lock ++ * (held by our caller isotp_rcv()) rules out a concurrent claim, ++ * so a plain recheck is enough here. ++ */ ++ if (so->tx.state != ISOTP_WAIT_FC && ++ so->tx.state != ISOTP_WAIT_FIRST_FC) ++ return 1; ++ + if ((cf->len < ae + FC_CONTENT_SZ) || + ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { +@@ -414,7 +424,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) + so->tx.bs = 0; + so->tx.state = ISOTP_SENDING; + /* send CF frame and enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); + break; +@@ -567,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; ++ + /* CFs are never longer than the FF */ + if (cf->len > so->rx.ll_dl) + return 1; +@@ -856,20 +874,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + + /* only handle my own local echo CF/SF skb's (no FF!) */ +- if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) ++ if (skb->sk != sk) + return; + ++ /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock ++ * (no isotp_rcv() caller here), so take it ourselves ++ */ ++ spin_lock(&so->rx_lock); ++ ++ /* so->cfecho may since belong to a new transfer; recheck under lock */ ++ if (so->cfecho != *(u32 *)cf->data) ++ goto out_unlock; ++ + /* cancel local echo timeout */ +- hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + + /* local echo skb with consecutive frame has been consumed */ + so->cfecho = 0; + ++ /* claiming a transfer also takes so->rx_lock, so a plain recheck ++ * is enough: so->tx.state can't have flipped to ISOTP_SENDING for ++ * a new claim while we're still in here ++ */ ++ if (so->tx.state != ISOTP_SENDING) ++ goto out_unlock; ++ + if (so->tx.idx >= so->tx.len) { + /* we are done */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); +- return; ++ goto out_unlock; + } + + if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { +@@ -877,53 +911,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) + so->tx.state = ISOTP_WAIT_FC; + hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); +- return; ++ goto out_unlock; + } + + /* no gap between data frames needed => use burst mode */ + if (!so->tx_gap) { + /* enable echo timeout handling */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + isotp_send_cframe(so); +- return; ++ goto out_unlock; + } + + /* start timer to send next consecutive frame with correct delay */ + hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT); ++ ++out_unlock: ++ spin_unlock(&so->rx_lock); + } + +-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get ++ * cancelled under so->rx_lock elsewhere, so this must stay lock-free to ++ * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting ++ * a new transfer with an error from the one that just timed out. ++ */ ++static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) + { +- struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, +- txtimer); + struct sock *sk = &so->sk; ++ u32 gen = READ_ONCE(so->tx_gen); ++ u32 old_state = READ_ONCE(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 the timeout if the state is still unchanged */ ++ 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); ++ if (READ_ONCE(so->tx_gen) == gen) { ++ /* 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; + } + ++/* so->txtimer: fires when a Flow Control frame does not arrive in time */ ++static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ txtimer); ++ ++ return isotp_tx_timeout(so); ++} ++ ++/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */ ++static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer) ++{ ++ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, ++ echotimer); ++ ++ return isotp_tx_timeout(so); ++} ++ + static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) + { + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txfrtimer); + + /* start echo timeout handling and cover below protocol error */ +- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), ++ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + HRTIMER_MODE_REL_SOFT); + + /* cfecho should be consumed by isotp_rcv_echo() here */ +@@ -943,13 +1007,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; + s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; ++ struct hrtimer *tx_hrt = &so->echotimer; ++ u32 new_state = ISOTP_SENDING; + int off; + int err; + + if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) + return -EADDRNOTAVAIL; + +- while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) { ++ /* claim the socket under so->rx_lock: this serializes the claim ++ * with the RX path and with sendmsg()'s own error paths below, so ++ * none of them can ever see a transfer mid-claim ++ */ ++ for (;;) { ++ spin_lock_bh(&so->rx_lock); ++ if (READ_ONCE(so->tx.state) == ISOTP_IDLE) ++ break; ++ spin_unlock_bh(&so->rx_lock); ++ + /* we do not support multiple buffers - for now */ + if (msg->msg_flags & MSG_DONTWAIT) + return -EAGAIN; +@@ -958,11 +1033,23 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + return -EADDRNOTAVAIL; + + /* wait for complete transmission of current pdu */ +- err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); ++ err = wait_event_interruptible(so->wait, ++ so->tx.state == ISOTP_IDLE); + if (err) +- goto err_event_drop; ++ return err; + } + ++ /* new transfer: bump so->tx_gen and drain the old one's timers, ++ * still under the so->rx_lock we just claimed the socket with ++ */ ++ WRITE_ONCE(so->tx.state, ISOTP_SENDING); ++ WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); ++ hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); ++ hrtimer_cancel(&so->txfrtimer); ++ so->cfecho = 0; ++ spin_unlock_bh(&so->rx_lock); ++ + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ +@@ -1073,18 +1160,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + so->cfecho = *(u32 *)cf->data; + } else { + /* standard flow control check */ +- so->tx.state = ISOTP_WAIT_FIRST_FC; ++ new_state = ISOTP_WAIT_FIRST_FC; + + /* start timeout for FC */ + hrtimer_sec = ISOTP_FC_TIMEOUT; ++ tx_hrt = &so->txtimer; + + /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ + so->cfecho = 0; + } + } + +- hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0), ++ spin_lock_bh(&so->rx_lock); ++ if (so->tx.state == ISOTP_SHUTDOWN) { ++ /* isotp_release() has since taken over and already drained ++ * our timers - don't send into a socket that's going away ++ */ ++ spin_unlock_bh(&so->rx_lock); ++ kfree_skb(skb); ++ dev_put(dev); ++ wake_up_interruptible(&so->wait); ++ return -EADDRNOTAVAIL; ++ } ++ /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ ++ so->tx.state = new_state; ++ hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), + HRTIMER_MODE_REL_SOFT); ++ spin_unlock_bh(&so->rx_lock); + + /* send the first or only CAN frame */ + cf->flags = so->ll.tx_flags; +@@ -1097,13 +1199,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + pr_notice_once("can-isotp: %s: can_send_ret %pe\n", + __func__, ERR_PTR(err)); + ++ spin_lock_bh(&so->rx_lock); + /* no transmission -> no timeout monitoring */ +- hrtimer_cancel(&so->txtimer); +- +- /* reset consecutive frame echo tag */ +- so->cfecho = 0; +- +- goto err_out_drop; ++ hrtimer_cancel(tx_hrt); ++ goto err_out_drop_locked; + } + + if (wait_tx_done) { +@@ -1119,14 +1218,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) + + return size; + ++err_out_drop: ++ /* claimed but nothing sent yet - no timer to cancel */ ++ spin_lock_bh(&so->rx_lock); ++ goto err_out_drop_locked; + err_event_drop: +- /* got signal: force tx state machine to be idle */ +- so->tx.state = ISOTP_IDLE; ++ /* interrupted waiting on our own transfer - drain its timers */ ++ spin_lock_bh(&so->rx_lock); + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); +-err_out_drop: +- /* drop this PDU and unlock a potential wait queue */ ++ hrtimer_cancel(&so->echotimer); ++err_out_drop_locked: ++ /* release the claim; so->rx_lock still held from above */ ++ so->cfecho = 0; + so->tx.state = ISOTP_IDLE; ++ spin_unlock_bh(&so->rx_lock); + wake_up_interruptible(&so->wait); + + return err; +@@ -1188,13 +1294,20 @@ static int isotp_release(struct socket *sock) + so = isotp_sk(sk); + net = sock_net(sk); + +- /* wait for complete transmission of current pdu */ +- while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 && +- cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE) ++ /* best-effort: wait for a running pdu to finish, but don't block on ++ * it forever - give up after the first signal ++ */ ++ while (so->tx.state != ISOTP_IDLE && ++ wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) + ; + +- /* force state machines to be idle also when a signal occurred */ ++ /* claim the socket under so->rx_lock like sendmsg() does, so its ++ * claim can't race the forced ISOTP_SHUTDOWN below; force it ++ * unconditionally, even when a signal cut the wait above short ++ */ ++ spin_lock_bh(&so->rx_lock); + so->tx.state = ISOTP_SHUTDOWN; ++ spin_unlock_bh(&so->rx_lock); + so->rx.state = ISOTP_IDLE; + + spin_lock(&isotp_notifier_lock); +@@ -1240,6 +1353,7 @@ static int isotp_release(struct socket *sock) + + hrtimer_cancel(&so->txfrtimer); + hrtimer_cancel(&so->txtimer); ++ hrtimer_cancel(&so->echotimer); + hrtimer_cancel(&so->rxtimer); + + sock_orphan(sk); +@@ -1682,6 +1796,8 @@ static int isotp_init(struct sock *sk) + so->rxtimer.function = isotp_rx_timer_handler; + hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txtimer.function = isotp_tx_timer_handler; ++ hrtimer_init(&so->echotimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ so->echotimer.function = isotp_echo_timer_handler; + hrtimer_init(&so->txfrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txfrtimer.function = isotp_txfr_timer_handler; + +-- +2.53.0 + diff --git a/queue-6.6/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch b/queue-6.6/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch new file mode 100644 index 0000000000..ee85a56825 --- /dev/null +++ b/queue-6.6/dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch @@ -0,0 +1,203 @@ +From 9f178c2e25319df6df428a77473b9b3386de5361 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 14:54:01 +0200 +Subject: dmaengine: sh: rz-dmac: Move interrupt request after everything is + set up + +From: Claudiu Beznea + +commit 731712403ddb39d1a76a11abf339a0615bc85de7 upstream. + +Once the interrupt is requested, the interrupt handler may run immediately. +Since the IRQ handler can access channel->ch_base, which is initialized +only after requesting the IRQ, this may lead to invalid memory access. +Likewise, the IRQ thread may access uninitialized data (the ld_free, +ld_queue, and ld_active lists), which may also lead to issues. + +Request the interrupts only after everything is set up. To keep the error +path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent(). + +Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC") +Cc: stable@vger.kernel.org +Reviewed-by: Frank Li +Tested-by: John Madieu +Signed-off-by: Claudiu Beznea +Tested-by: Tommaso Merciai +Link: https://patch.msgid.link/20260526084710.3491480-2-claudiu.beznea@kernel.org +[tm: Kept the channel->irq field in rz_dmac_chan_probe() instead of + upstream's local `irq` variable, as commit 04e227718ab8 + ("dmaengine: sh: rz-dmac: Make channel irq local") is not present + in this tree. Likewise kept platform_get_irq_byname() instead of + platform_get_irq_byname_optional() for the error IRQ in rz_dmac_probe(), + as commit 6b3a6b6dc074 ("dmaengine: sh: rz_dmac: make error interrupt + optional") is not present in this tree either; its early return on + failure becomes a goto err jump to match the new call order.] +Signed-off-by: Vinod Koul +Signed-off-by: Tommaso Merciai +Signed-off-by: Sasha Levin +--- + drivers/dma/sh/rz-dmac.c | 96 ++++++++++++++++------------------------ + 1 file changed, 38 insertions(+), 58 deletions(-) + +diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c +index e5d89bc1bb83e7..63bcec46b01d7b 100644 +--- a/drivers/dma/sh/rz-dmac.c ++++ b/drivers/dma/sh/rz-dmac.c +@@ -777,27 +777,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + channel->index = index; + channel->mid_rid = -EINVAL; + +- /* Request the channel interrupt. */ +- sprintf(pdev_irqname, "ch%u", index); +- channel->irq = platform_get_irq_byname(pdev, pdev_irqname); +- if (channel->irq < 0) +- return channel->irq; +- +- irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", +- dev_name(dmac->dev), index); +- if (!irqname) +- return -ENOMEM; +- +- ret = devm_request_threaded_irq(dmac->dev, channel->irq, +- rz_dmac_irq_handler, +- rz_dmac_irq_handler_thread, 0, +- irqname, channel); +- if (ret) { +- dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", +- channel->irq, ret); +- return ret; +- } +- + /* Set io base address for each channel */ + if (index < 8) { + channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET + +@@ -810,9 +789,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + } + + /* Allocate descriptors */ +- lmdesc = dma_alloc_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- &channel->lmdesc.base_dma, GFP_KERNEL); ++ lmdesc = dmam_alloc_coherent(&pdev->dev, ++ sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, ++ &channel->lmdesc.base_dma, GFP_KERNEL); + if (!lmdesc) { + dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n"); + return -ENOMEM; +@@ -828,7 +807,26 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, + INIT_LIST_HEAD(&channel->ld_free); + INIT_LIST_HEAD(&channel->ld_active); + +- return 0; ++ /* Request the channel interrupt. */ ++ sprintf(pdev_irqname, "ch%u", index); ++ channel->irq = platform_get_irq_byname(pdev, pdev_irqname); ++ if (channel->irq < 0) ++ return channel->irq; ++ ++ irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u", ++ dev_name(dmac->dev), index); ++ if (!irqname) ++ return -ENOMEM; ++ ++ ret = devm_request_threaded_irq(dmac->dev, channel->irq, ++ rz_dmac_irq_handler, ++ rz_dmac_irq_handler_thread, 0, ++ irqname, channel); ++ if (ret) ++ dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", ++ channel->irq, ret); ++ ++ return ret; + } + + static int rz_dmac_parse_of(struct device *dev, struct rz_dmac *dmac) +@@ -855,7 +853,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + const char *irqname = "error"; + struct dma_device *engine; + struct rz_dmac *dmac; +- int channel_num; + unsigned int i; + int ret; + int irq; +@@ -885,19 +882,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + if (IS_ERR(dmac->ext_base)) + return PTR_ERR(dmac->ext_base); + +- /* Register interrupt handler for error */ +- irq = platform_get_irq_byname(pdev, irqname); +- if (irq < 0) +- return irq; +- +- ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, +- irqname, NULL); +- if (ret) { +- dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", +- irq, ret); +- return ret; +- } +- + /* Initialize the channels. */ + INIT_LIST_HEAD(&dmac->engine.channels); + +@@ -923,6 +907,21 @@ static int rz_dmac_probe(struct platform_device *pdev) + goto err; + } + ++ /* Register interrupt handler for error */ ++ irq = platform_get_irq_byname(pdev, irqname); ++ if (irq < 0) { ++ ret = irq; ++ goto err; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0, ++ irqname, NULL); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n", ++ irq, ret); ++ goto err; ++ } ++ + /* Register the DMAC as a DMA provider for DT. */ + ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate, + NULL); +@@ -961,16 +960,6 @@ static int rz_dmac_probe(struct platform_device *pdev) + dma_register_err: + of_dma_controller_free(pdev->dev.of_node); + err: +- channel_num = i ? i - 1 : 0; +- for (i = 0; i < channel_num; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } +- + reset_control_assert(dmac->rstc); + err_pm_runtime_put: + pm_runtime_put(&pdev->dev); +@@ -983,18 +972,9 @@ static int rz_dmac_probe(struct platform_device *pdev) + static int rz_dmac_remove(struct platform_device *pdev) + { + struct rz_dmac *dmac = platform_get_drvdata(pdev); +- unsigned int i; + + dma_async_device_unregister(&dmac->engine); + of_dma_controller_free(pdev->dev.of_node); +- for (i = 0; i < dmac->n_channels; i++) { +- struct rz_dmac_chan *channel = &dmac->channels[i]; +- +- dma_free_coherent(&pdev->dev, +- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC, +- channel->lmdesc.base, +- channel->lmdesc.base_dma); +- } + reset_control_assert(dmac->rstc); + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); +-- +2.53.0 + diff --git a/queue-6.6/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch b/queue-6.6/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch new file mode 100644 index 0000000000..76533c8256 --- /dev/null +++ b/queue-6.6/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch @@ -0,0 +1,43 @@ +From 7ce46bd653ba5aaa90ab4a53e70508c9c7e3ebca Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 3 Jun 2026 17:37:49 +0900 +Subject: gpu: host1x: Fix use-after-free in host1x_bo_clear_cached_mappings + +From: Mikko Perttunen + +[ Upstream commit 266cddf7bd0f6c79b6c0633aef742a22bf70265b ] + +__host1x_bo_unpin() drops the last reference to the mapping and frees +it, so we can't dereference mapping afterwards. The cache itself +outlives the mapping, so use the cache local variable instead. + +Reported-by: Dan Carpenter +Closes: https://lore.kernel.org/linux-tegra/ah6ErK6f4kVudVIA@stanley.mountain/T/#u +Signed-off-by: Mikko Perttunen +Signed-off-by: Thierry Reding +Link: https://patch.msgid.link/20260603-host1x-bocache-leak-fix-v1-1-494101dbfd30@nvidia.com +Signed-off-by: Sasha Levin +--- + drivers/gpu/host1x/bus.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c +index 6b8b7395a4189e..8a4e0738014d6c 100644 +--- a/drivers/gpu/host1x/bus.c ++++ b/drivers/gpu/host1x/bus.c +@@ -1006,10 +1006,10 @@ void host1x_bo_clear_cached_mappings(struct host1x_bo *bo) + if (WARN_ON(!cache)) + continue; + +- mutex_lock(&mapping->cache->lock); ++ mutex_lock(&cache->lock); + WARN_ON(kref_read(&mapping->ref) != 1); + __host1x_bo_unpin(&mapping->ref); +- mutex_unlock(&mapping->cache->lock); ++ mutex_unlock(&cache->lock); + } + } + EXPORT_SYMBOL(host1x_bo_clear_cached_mappings); +-- +2.53.0 + diff --git a/queue-6.6/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch b/queue-6.6/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch new file mode 100644 index 0000000000..c00b82db38 --- /dev/null +++ b/queue-6.6/input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch @@ -0,0 +1,114 @@ +From 9c11aafab37c1fa18f013d8e52a3dbd06ec33ab0 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 8 Apr 2026 09:03:59 -0700 +Subject: Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data() + +From: Seungjin Bae + +[ Upstream commit 875115b82c295277b81b6dfee7debc725f44e854 ] + +The `ims_pcu_process_data()` processes incoming URB data byte by byte. +However, it fails to check if the `read_pos` index exceeds +IMS_PCU_BUF_SIZE. + +If a malicious USB device sends a packet larger than IMS_PCU_BUF_SIZE, +`read_pos` will increment indefinitely. Moreover, since `read_pos` is +located immediately after `read_buf`, the attacker can overwrite +`read_pos` itself to arbitrarily control the index. + +This manipulated `read_pos` is subsequently used in +`ims_pcu_handle_response()` to copy data into `cmd_buf`, leading to a +heap buffer overflow. + +Specifically, an attacker can overwrite the `cmd_done.wait.head` located +at offset 136 relative to `cmd_buf` in the `ims_pcu_handle_response()`. +Consequently, when the driver calls `complete(&pcu->cmd_done)`, it +triggers a control flow hijack by using the manipulated pointer. + +Fix this by adding a bounds check for `read_pos` before writing to +`read_buf`. If the packet is too long, discard it, log a warning, +and reset the parser state. + +Fixes: 628329d524743 ("Input: add IMS Passenger Control Unit driver") +Co-developed-by: Sanghoon Choi +Signed-off-by: Sanghoon Choi +Signed-off-by: Seungjin Bae +Link: https://patch.msgid.link/20251221211442.841549-2-eeodqql09@gmail.com +[dtor: factor out resetting packet state, reset checksum as well] +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 32 ++++++++++++++++++++++++++------ + 1 file changed, 26 insertions(+), 6 deletions(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index 2bac9d9c7b0c9c..af1dfc08b10b25 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -448,6 +448,14 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + } + } + ++static void ims_pcu_reset_packet(struct ims_pcu *pcu) ++{ ++ pcu->have_stx = true; ++ pcu->have_dle = false; ++ pcu->read_pos = 0; ++ pcu->check_sum = 0; ++} ++ + static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + { + int i; +@@ -460,6 +468,14 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + continue; + + if (pcu->have_dle) { ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->have_dle = false; + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; +@@ -472,10 +488,8 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + dev_warn(pcu->dev, + "Unexpected STX at byte %d, discarding old data\n", + pcu->read_pos); ++ ims_pcu_reset_packet(pcu); + pcu->have_stx = true; +- pcu->have_dle = false; +- pcu->read_pos = 0; +- pcu->check_sum = 0; + break; + + case IMS_PCU_PROTOCOL_DLE: +@@ -495,12 +509,18 @@ static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) + ims_pcu_handle_response(pcu); + } + +- pcu->have_stx = false; +- pcu->have_dle = false; +- pcu->read_pos = 0; ++ ims_pcu_reset_packet(pcu); + break; + + default: ++ if (pcu->read_pos >= IMS_PCU_BUF_SIZE) { ++ dev_warn(pcu->dev, ++ "Packet too long (%d bytes), discarding\n", ++ pcu->read_pos); ++ ims_pcu_reset_packet(pcu); ++ continue; ++ } ++ + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + break; +-- +2.53.0 + diff --git a/queue-6.6/input-ims-pcu-fix-logic-error-in-packet-reset.patch b/queue-6.6/input-ims-pcu-fix-logic-error-in-packet-reset.patch new file mode 100644 index 0000000000..badfd289fe --- /dev/null +++ b/queue-6.6/input-ims-pcu-fix-logic-error-in-packet-reset.patch @@ -0,0 +1,42 @@ +From 016625a1085ec23c05a78738fb60e62e803cef08 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 22 May 2026 10:29:41 -0700 +Subject: Input: ims-pcu - fix logic error in packet reset + +From: Dmitry Torokhov + +[ Upstream commit 2c9b85a14abb4811e8d4773ccd13559e59792efb ] + +ims_pcu_reset_packet() incorrectly sets have_stx to true, which implies +that the start-of-packet delimiter has already been received. This +causes the protocol parser to skip waiting for the next STX byte and +potentially process garbage data. + +Correctly set have_stx to false when resetting the packet state. + +Fixes: 875115b82c29 ("Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data()") +Cc: stable@vger.kernel.org +Reported-by: Sashiko bot +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +--- + drivers/input/misc/ims-pcu.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +index af1dfc08b10b25..6b2aeaa50812f2 100644 +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -450,7 +450,7 @@ static void ims_pcu_handle_response(struct ims_pcu *pcu) + + static void ims_pcu_reset_packet(struct ims_pcu *pcu) + { +- pcu->have_stx = true; ++ pcu->have_stx = false; + pcu->have_dle = false; + pcu->read_pos = 0; + pcu->check_sum = 0; +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index 74f7e26c5a..faa55462d0 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -11,3 +11,19 @@ kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch kvm-x86-mmu-fix-use-after-free-on-vendor-module-reload.patch +can-bcm-add-locking-when-updating-filter-and-timer-v.patch +can-bcm-fix-can-frame-rx-tx-statistics.patch +can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch +can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch +can-bcm-add-missing-device-refcount-for-can-filter-r.patch +can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch +can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch +can-bcm-track-a-single-source-interface-for-anydev-t.patch +can-isotp-fix-use-after-free-race-with-concurrent-ne.patch +can-isotp-serialize-tx-state-transitions-under-so-rx.patch +dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch +accel-ivpu-reject-firmware-log-with-size-smaller-tha.patch +gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch +xprtrdma-clear-receive-side-ownership-pointers-on-re.patch +input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch +input-ims-pcu-fix-logic-error-in-packet-reset.patch diff --git a/queue-6.6/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch b/queue-6.6/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch new file mode 100644 index 0000000000..2c7c56a8ba --- /dev/null +++ b/queue-6.6/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch @@ -0,0 +1,110 @@ +From b6c0b95e96d9ce84c85df7f7413ed72277439aed Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 10:14:04 -0400 +Subject: xprtrdma: Clear receive-side ownership pointers on release + +From: Chuck Lever + +[ Upstream commit 2ae8e7afbc63bf84243367f89eb43571f0345a74 ] + +Three small ownership-state cleanups land the transport in a +state that lets future reviewers reason about each pointer +locally rather than tracing the whole reply path: + +rpcrdma_rep_put() clears rep->rr_rqst before the rep enters +rb_free_reps so that no rep on the free list still carries a +stale rqst pointer. rpcrdma_reply_handler() and +rpcrdma_unpin_rqst() are the only sites that set rr_rqst; +rpcrdma_reply_handler() hands the rep through +rpcrdma_rep_put(), and rpcrdma_unpin_rqst() NULLs rr_rqst +directly because its error path abandons the rep for +teardown cleanup rather than returning it to rb_free_reps. + +rpcrdma_reply_put() NULLs req->rl_reply before calling +rpcrdma_rep_put(). The previous order placed the rep on +rb_free_reps while req->rl_reply still pointed at it; the +window was harmless because xprt_rdma_free_slot() holds the +req exclusively across the pair, but closing it makes the +invariant 'rep on rb_free_reps implies no req references it' +strictly checkable. + +rpcrdma_sendctx_unmap() and rpcrdma_sendctx_cancel() clear +req->rl_sendctx after dropping the sendctx pointer in the +sendctx ring. Without this, req->rl_sendctx survives across +Send completion and points at a sendctx that may already have +been reassigned by rpcrdma_sendctx_get_locked() to a different +req. No caller dereferences the stale pointer today -- +rpcrdma_prepare_send_sges() overwrites it before the next +Send -- but a NULL is a more honest representation of 'the +Send is no longer outstanding' and lets the assertion patch +that follows trip on any future regression. + +Signed-off-by: Chuck Lever +Signed-off-by: Anna Schumaker +Signed-off-by: Sasha Levin +--- + net/sunrpc/xprtrdma/rpc_rdma.c | 4 ++++ + net/sunrpc/xprtrdma/verbs.c | 12 ++++++++++-- + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c +index e201b37578a70e..aa57e057ff451f 100644 +--- a/net/sunrpc/xprtrdma/rpc_rdma.c ++++ b/net/sunrpc/xprtrdma/rpc_rdma.c +@@ -542,6 +542,7 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + rpcrdma_req_put(req); + } + +@@ -550,8 +551,11 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + */ + static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) + { ++ struct rpcrdma_req *req = sc->sc_req; ++ + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + } + + /* Prepare an SGE for the RPC-over-RDMA transport header. +diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c +index 27bb176f082f63..a97f0b18ac4294 100644 +--- a/net/sunrpc/xprtrdma/verbs.c ++++ b/net/sunrpc/xprtrdma/verbs.c +@@ -1067,9 +1067,15 @@ static struct rpcrdma_rep *rpcrdma_rep_get_locked(struct rpcrdma_buffer *buf) + * @buf: buffer pool + * @rep: rep to release + * ++ * The rep's transient association with an rpc_rqst, established ++ * by rpcrdma_reply_handler() and torn down here, must not survive ++ * onto rb_free_reps: rpcrdma_post_recvs() pulls reps from the free ++ * list to re-post them, and a non-NULL rr_rqst on a free-listed rep ++ * would imply the rep is still referenced by a req. + */ + void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep) + { ++ rep->rr_rqst = NULL; + llist_add(&rep->rr_node, &buf->rb_free_reps); + } + +@@ -1252,9 +1258,11 @@ rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt) + */ + void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) + { +- if (req->rl_reply) { +- rpcrdma_rep_put(buffers, req->rl_reply); ++ struct rpcrdma_rep *rep = req->rl_reply; ++ ++ if (rep) { + req->rl_reply = NULL; ++ rpcrdma_rep_put(buffers, rep); + } + /* I2: rl_reply NULL after the put closes the + * 'rep on rb_free_reps still referenced by req' window. +-- +2.53.0 + diff --git a/queue-7.1/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch b/queue-7.1/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch new file mode 100644 index 0000000000..959a0baa57 --- /dev/null +++ b/queue-7.1/crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch @@ -0,0 +1,44 @@ +From 49b831a033720b4388bc2e321b0fc6e0994f68ed Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 15 May 2026 11:34:52 +0900 +Subject: crypto: tegra - Don't touch bo refcount in host1x bo pin/unpin + +From: Mikko Perttunen + +[ Upstream commit f8c9c57d750346abd213ffed2ae3cacb0268e9f1 ] + +Since commit "gpu: host1x: Allow entries in BO caches to be freed", +host1x_bo_pin() and host1x_bo_unpin() handle the bo's refcount +themselves. .pin/.unpin callbacks should not adjust it. + +Signed-off-by: Mikko Perttunen +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +--- + drivers/crypto/tegra/tegra-se-main.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/drivers/crypto/tegra/tegra-se-main.c b/drivers/crypto/tegra/tegra-se-main.c +index e8d8c3a23d7adb..d2f518ef9a103f 100644 +--- a/drivers/crypto/tegra/tegra-se-main.c ++++ b/drivers/crypto/tegra/tegra-se-main.c +@@ -52,7 +52,7 @@ tegra_se_cmdbuf_pin(struct device *dev, struct host1x_bo *bo, enum dma_data_dire + return ERR_PTR(-ENOMEM); + + kref_init(&map->ref); +- map->bo = host1x_bo_get(bo); ++ map->bo = bo; + map->direction = direction; + map->dev = dev; + +@@ -93,7 +93,6 @@ static void tegra_se_cmdbuf_unpin(struct host1x_bo_mapping *map) + dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0); + sg_free_table(map->sgt); + kfree(map->sgt); +- host1x_bo_put(map->bo); + + kfree(map); + } +-- +2.53.0 + diff --git a/queue-7.1/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch b/queue-7.1/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch new file mode 100644 index 0000000000..f324ec3b78 --- /dev/null +++ b/queue-7.1/gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch @@ -0,0 +1,43 @@ +From a8786a94ae4ee4314be261630cacfc8c62a22db1 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 3 Jun 2026 17:37:49 +0900 +Subject: gpu: host1x: Fix use-after-free in host1x_bo_clear_cached_mappings + +From: Mikko Perttunen + +[ Upstream commit 266cddf7bd0f6c79b6c0633aef742a22bf70265b ] + +__host1x_bo_unpin() drops the last reference to the mapping and frees +it, so we can't dereference mapping afterwards. The cache itself +outlives the mapping, so use the cache local variable instead. + +Reported-by: Dan Carpenter +Closes: https://lore.kernel.org/linux-tegra/ah6ErK6f4kVudVIA@stanley.mountain/T/#u +Signed-off-by: Mikko Perttunen +Signed-off-by: Thierry Reding +Link: https://patch.msgid.link/20260603-host1x-bocache-leak-fix-v1-1-494101dbfd30@nvidia.com +Signed-off-by: Sasha Levin +--- + drivers/gpu/host1x/bus.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c +index c273a4476f2370..a8cddb3425b428 100644 +--- a/drivers/gpu/host1x/bus.c ++++ b/drivers/gpu/host1x/bus.c +@@ -1012,10 +1012,10 @@ void host1x_bo_clear_cached_mappings(struct host1x_bo *bo) + if (WARN_ON(!cache)) + continue; + +- mutex_lock(&mapping->cache->lock); ++ mutex_lock(&cache->lock); + WARN_ON(kref_read(&mapping->ref) != 1); + __host1x_bo_unpin(&mapping->ref); +- mutex_unlock(&mapping->cache->lock); ++ mutex_unlock(&cache->lock); + } + } + EXPORT_SYMBOL(host1x_bo_clear_cached_mappings); +-- +2.53.0 + diff --git a/queue-7.1/series b/queue-7.1/series index 00801c7732..b1e281a171 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -7,3 +7,6 @@ kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch kvm-x86-mmu-fix-use-after-free-on-vendor-module-reload.patch +gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch +crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch +xprtrdma-clear-receive-side-ownership-pointers-on-re.patch diff --git a/queue-7.1/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch b/queue-7.1/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch new file mode 100644 index 0000000000..da8a28d784 --- /dev/null +++ b/queue-7.1/xprtrdma-clear-receive-side-ownership-pointers-on-re.patch @@ -0,0 +1,110 @@ +From 25c8d70bccdabeef0a328a45e038f8e345640762 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 10:14:04 -0400 +Subject: xprtrdma: Clear receive-side ownership pointers on release + +From: Chuck Lever + +[ Upstream commit 2ae8e7afbc63bf84243367f89eb43571f0345a74 ] + +Three small ownership-state cleanups land the transport in a +state that lets future reviewers reason about each pointer +locally rather than tracing the whole reply path: + +rpcrdma_rep_put() clears rep->rr_rqst before the rep enters +rb_free_reps so that no rep on the free list still carries a +stale rqst pointer. rpcrdma_reply_handler() and +rpcrdma_unpin_rqst() are the only sites that set rr_rqst; +rpcrdma_reply_handler() hands the rep through +rpcrdma_rep_put(), and rpcrdma_unpin_rqst() NULLs rr_rqst +directly because its error path abandons the rep for +teardown cleanup rather than returning it to rb_free_reps. + +rpcrdma_reply_put() NULLs req->rl_reply before calling +rpcrdma_rep_put(). The previous order placed the rep on +rb_free_reps while req->rl_reply still pointed at it; the +window was harmless because xprt_rdma_free_slot() holds the +req exclusively across the pair, but closing it makes the +invariant 'rep on rb_free_reps implies no req references it' +strictly checkable. + +rpcrdma_sendctx_unmap() and rpcrdma_sendctx_cancel() clear +req->rl_sendctx after dropping the sendctx pointer in the +sendctx ring. Without this, req->rl_sendctx survives across +Send completion and points at a sendctx that may already have +been reassigned by rpcrdma_sendctx_get_locked() to a different +req. No caller dereferences the stale pointer today -- +rpcrdma_prepare_send_sges() overwrites it before the next +Send -- but a NULL is a more honest representation of 'the +Send is no longer outstanding' and lets the assertion patch +that follows trip on any future regression. + +Signed-off-by: Chuck Lever +Signed-off-by: Anna Schumaker +Signed-off-by: Sasha Levin +--- + net/sunrpc/xprtrdma/rpc_rdma.c | 4 ++++ + net/sunrpc/xprtrdma/verbs.c | 12 ++++++++++-- + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c +index e8b5fc86a01bbf..81070b78512b1e 100644 +--- a/net/sunrpc/xprtrdma/rpc_rdma.c ++++ b/net/sunrpc/xprtrdma/rpc_rdma.c +@@ -493,6 +493,7 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + rpcrdma_req_put(req); + } + +@@ -501,8 +502,11 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) + */ + static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) + { ++ struct rpcrdma_req *req = sc->sc_req; ++ + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; ++ req->rl_sendctx = NULL; + } + + /* Prepare an SGE for the RPC-over-RDMA transport header. +diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c +index 0ac82f26bf6ec9..cbd20bef367d96 100644 +--- a/net/sunrpc/xprtrdma/verbs.c ++++ b/net/sunrpc/xprtrdma/verbs.c +@@ -1095,9 +1095,15 @@ static struct rpcrdma_rep *rpcrdma_rep_get_locked(struct rpcrdma_buffer *buf) + * @buf: buffer pool + * @rep: rep to release + * ++ * The rep's transient association with an rpc_rqst, established ++ * by rpcrdma_reply_handler() and torn down here, must not survive ++ * onto rb_free_reps: rpcrdma_post_recvs() pulls reps from the free ++ * list to re-post them, and a non-NULL rr_rqst on a free-listed rep ++ * would imply the rep is still referenced by a req. + */ + void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep) + { ++ rep->rr_rqst = NULL; + llist_add(&rep->rr_node, &buf->rb_free_reps); + } + +@@ -1280,9 +1286,11 @@ rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt) + */ + void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) + { +- if (req->rl_reply) { +- rpcrdma_rep_put(buffers, req->rl_reply); ++ struct rpcrdma_rep *rep = req->rl_reply; ++ ++ if (rep) { + req->rl_reply = NULL; ++ rpcrdma_rep_put(buffers, rep); + } + /* I2: rl_reply NULL after the put closes the + * 'rep on rb_free_reps still referenced by req' window. +-- +2.53.0 +