diff options
| author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-07-24 16:17:26 +0200 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-07-24 16:17:26 +0200 |
| commit | b62ed4c93ad71374b54d2c3a72cbc67b506f580c (patch) | |
| tree | 6ca6ab974bbce902fc9de300fea6aa056170850f /net/can/bcm.c | |
| parent | 5895db67c12464003afd16c08049b73aa09e58ea (diff) | |
| parent | 221fc2f4d0eda59d02af2e751a9282fa013a8e97 (diff) | |
Merge v6.18.40linux-rolling-lts
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'net/can/bcm.c')
| -rw-r--r-- | net/can/bcm.c | 127 |
1 files changed, 101 insertions, 26 deletions
diff --git a/net/can/bcm.c b/net/can/bcm.c index 756acfd20c6c..a40519638a26 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -58,6 +58,7 @@ #include <linux/can/skb.h> #include <linux/can/bcm.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include <linux/spinlock.h> #include <net/sock.h> #include <net/net_namespace.h> @@ -91,6 +92,8 @@ MODULE_ALIAS("can-proto-2"); #define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex) +static struct workqueue_struct *bcm_wq; + /* * easy access to the first 64 bit of can(fd)_frame payload. cp->data is * 64 bit aligned so the offset has to be multiples of 8 which is ensured @@ -104,6 +107,7 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset) struct bcm_op { struct list_head list; struct rcu_head rcu; + struct work_struct work; int ifindex; canid_t can_id; u32 flags; @@ -256,7 +260,7 @@ static int bcm_proc_show(struct seq_file *m, void *v) (reduction == 100) ? "near " : "", reduction); } - list_for_each_entry(op, &bo->tx_ops, list) { + list_for_each_entry_rcu(op, &bo->tx_ops, list) { seq_printf(m, "tx_op: %03X %s ", op->can_id, bcm_proc_getifname(net, ifname, op->ifindex)); @@ -788,9 +792,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops, return NULL; } -static void bcm_free_op_rcu(struct rcu_head *rcu_head) +static void bcm_free_op_work(struct work_struct *work) { - struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + struct bcm_op *op = container_of(work, struct bcm_op, work); + + hrtimer_cancel(&op->timer); + hrtimer_cancel(&op->thrtimer); if ((op->frames) && (op->frames != &op->sframe)) kfree(op->frames); @@ -798,9 +805,23 @@ static void bcm_free_op_rcu(struct rcu_head *rcu_head) if ((op->last_frames) && (op->last_frames != &op->last_sframe)) kfree(op->last_frames); + /* the last possible access to op->timer/op->thrtimer has now + * happened above via hrtimer_cancel() - op->sk is no longer + * needed by any pending timer callback, so drop our reference + */ + sock_put(op->sk); + kfree(op); } +static void bcm_free_op_rcu(struct rcu_head *rcu_head) +{ + struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + + INIT_WORK(&op->work, bcm_free_op_work); + queue_work(bcm_wq, &op->work); +} + static void bcm_remove_op(struct bcm_op *op) { hrtimer_cancel(&op->timer); @@ -925,6 +946,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, struct bcm_sock *bo = bcm_sk(sk); struct bcm_op *op; struct canfd_frame *cf; + bool add_op_to_list = false; unsigned int i; int err; @@ -1055,6 +1077,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; + sock_hold(sk); op->ifindex = ifindex; /* initialize uninitialized (kzalloc) structure */ @@ -1065,8 +1088,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, hrtimer_setup(&op->thrtimer, hrtimer_dummy_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - /* add this bcm_op to the list of the tx_ops */ - list_add(&op->list, &bo->tx_ops); + add_op_to_list = true; } /* if ((op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex))) */ @@ -1088,6 +1110,10 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, op->flags |= TX_ANNOUNCE; } + /* add this bcm_op to the list of the tx_ops? */ + if (add_op_to_list) + list_add_rcu(&op->list, &bo->tx_ops); + if (op->flags & TX_ANNOUNCE) bcm_can_tx(op); @@ -1216,6 +1242,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; + sock_hold(sk); op->ifindex = ifindex; /* ifindex for timeout events w/o previous frame reception */ @@ -1227,9 +1254,6 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, hrtimer_setup(&op->thrtimer, bcm_rx_thr_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - /* add this bcm_op to the list of the rx_ops */ - list_add(&op->list, &bo->rx_ops); - /* call can_rx_register() */ do_rx_register = 1; @@ -1295,6 +1319,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, op->rx_reg_dev = dev; dev_put(dev); + } else { + /* the requested device is gone - do not + * silently succeed without registering + */ + err = -ENODEV; } } else @@ -1303,10 +1332,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, bcm_rx_handler, op, "bcm", sk); if (err) { /* this bcm rx op is broken -> remove it */ - list_del_rcu(&op->list); bcm_remove_op(op); return err; } + + /* add this bcm_op to the list of the rx_ops */ + list_add_rcu(&op->list, &bo->rx_ops); } return msg_head->nframes * op->cfsiz + MHSIZ; @@ -1364,12 +1395,13 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { struct sock *sk = sock->sk; struct bcm_sock *bo = bcm_sk(sk); - int ifindex = bo->ifindex; /* default ifindex for this bcm_op */ + int ifindex; struct bcm_msg_head msg_head; int cfsiz; int ret; /* read bytes or error codes as return value */ - if (!bo->bound) + /* Lockless fast-path check for bound socket */ + if (!READ_ONCE(bo->bound)) return -ENOTCONN; /* check for valid message length from userspace */ @@ -1385,17 +1417,38 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) if ((size - MHSIZ) % cfsiz) return -EINVAL; + lock_sock(sk); + + /* Re-validate under the socket lock: a concurrent bcm_notify() + * may have unbound this socket (device removal) after the + * lockless fast-path check above. bo->ifindex is only ever + * mutated under lock_sock(), so reading it here - instead of + * before taking the lock - guarantees it can't be observed + * torn against bo->bound. + */ + if (!bo->bound) { + ret = -ENOTCONN; + goto out_release; + } + + /* default ifindex for this bcm_op */ + ifindex = bo->ifindex; + /* check for alternative ifindex for this bcm_op */ if (!ifindex && msg->msg_name) { /* no bound device as default => check msg_name */ DECLARE_SOCKADDR(struct sockaddr_can *, addr, msg->msg_name); - if (msg->msg_namelen < BCM_MIN_NAMELEN) - return -EINVAL; + if (msg->msg_namelen < BCM_MIN_NAMELEN) { + ret = -EINVAL; + goto out_release; + } - if (addr->can_family != AF_CAN) - return -EINVAL; + if (addr->can_family != AF_CAN) { + ret = -EINVAL; + goto out_release; + } /* ifindex from sendto() */ ifindex = addr->can_ifindex; @@ -1404,20 +1457,21 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) struct net_device *dev; dev = dev_get_by_index(sock_net(sk), ifindex); - if (!dev) - return -ENODEV; + if (!dev) { + ret = -ENODEV; + goto out_release; + } if (dev->type != ARPHRD_CAN) { dev_put(dev); - return -ENODEV; + ret = -ENODEV; + goto out_release; } dev_put(dev); } } - lock_sock(sk); - switch (msg_head.opcode) { case TX_SETUP: @@ -1467,6 +1521,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) break; } +out_release: release_sock(sk); return ret; @@ -1503,7 +1558,12 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, bo->bcm_proc_read = NULL; } #endif - bo->bound = 0; + /* Paired with the lockless fast-path check in + * bcm_sendmsg(); bo->ifindex itself is only ever + * accessed under lock_sock() so it needs no + * annotation. + */ + WRITE_ONCE(bo->bound, 0); bo->ifindex = 0; notify_enodev = 1; } @@ -1607,8 +1667,10 @@ static int bcm_release(struct socket *sock) remove_proc_entry(bo->procname, net->can.bcmproc_dir); #endif /* CONFIG_PROC_FS */ - list_for_each_entry_safe(op, next, &bo->tx_ops, list) + list_for_each_entry_safe(op, next, &bo->tx_ops, list) { + list_del_rcu(&op->list); bcm_remove_op(op); + } list_for_each_entry_safe(op, next, &bo->rx_ops, list) { /* @@ -1639,12 +1701,14 @@ static int bcm_release(struct socket *sock) synchronize_rcu(); - list_for_each_entry_safe(op, next, &bo->rx_ops, list) + list_for_each_entry_safe(op, next, &bo->rx_ops, list) { + list_del_rcu(&op->list); bcm_remove_op(op); + } /* remove device reference */ if (bo->bound) { - bo->bound = 0; + WRITE_ONCE(bo->bound, 0); bo->ifindex = 0; } @@ -1714,7 +1778,10 @@ static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len, } #endif /* CONFIG_PROC_FS */ - bo->bound = 1; + /* bo->ifindex above is fully assigned before this point; pairs + * with the lockless fast-path check in bcm_sendmsg() + */ + WRITE_ONCE(bo->bound, 1); fail: release_sock(sk); @@ -1830,11 +1897,15 @@ static int __init bcm_module_init(void) { int err; + bcm_wq = alloc_workqueue("can-bcm-wq", WQ_UNBOUND, 0); + if (!bcm_wq) + return -ENOMEM; + pr_info("can: broadcast manager protocol\n"); err = register_pernet_subsys(&canbcm_pernet_ops); if (err) - return err; + goto register_pernet_failed; err = register_netdevice_notifier(&canbcm_notifier); if (err) @@ -1852,6 +1923,8 @@ register_proto_failed: unregister_netdevice_notifier(&canbcm_notifier); register_notifier_failed: unregister_pernet_subsys(&canbcm_pernet_ops); +register_pernet_failed: + destroy_workqueue(bcm_wq); return err; } @@ -1860,6 +1933,8 @@ static void __exit bcm_module_exit(void) can_proto_unregister(&bcm_can_proto); unregister_netdevice_notifier(&canbcm_notifier); unregister_pernet_subsys(&canbcm_pernet_ops); + rcu_barrier(); + destroy_workqueue(bcm_wq); } module_init(bcm_module_init); |
