mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge patch series "can: protect the receive lists with a per-device lock"
Oliver Hartkopp <socketcan@hartkopp.net> says: This series fixes a use-after-free in CAN_ISOTP and CAN_BCM that can be triggered when a CAN device is moved to another network namespace while a socket bound to this device is released [1]. Patch 2 makes isotp_bind() check the return values of can_rx_register(), so that bind() does not succeed with missing receivers. Patch 4 copies the CAN_RAW and CAN_ISOTP specific socket options from and to user space outside of lock_sock(), so that a stalled copy can not block raw_notify() waiting for lock_sock() under RTNL. Patches 5 and 6 fix the reset and the calculation of the CAN statistics. Patch 7 removes the pointers from the CAN specific proc output. As it changes the proc output ABI it is not intended for stable and has been moved to the end of the series, so that patches 1-6 can be backported without it. The code of patches 1 and 5-7 is unchanged since v4, apart from the rebase of patch 7 in v12. Only the commit message of patch 1 has been updated after the Sashiko review of v4 to describe the actual NULL pointer dereference in can_rx_unregister() and the CAN_ISOTP path that reaches it. The series is based on net/main. The idea behind patch 3: The receive lists of a CAN device are located in the device's can_ml_priv and travel with the device when it is moved to another network namespace. But they are protected by the per-netns rcvlists_lock, so the lock that protects a device's lists depends on the namespace the caller passes to can_rx_register() and can_rx_unregister(). can_rx_unregister() silently returns when this namespace does not match dev_net(dev) anymore. isotp_release() and bcm_release() leave the netdev notifier list before they can serialize against the notifier. When the device changes its namespace in this window, their filters are not removed and the remaining receivers point to the freed socket or bcm op. Taking RTNL in these release paths is no option, as CAN_RAW already holds RTNL while adding and removing up to CAN_RAW_FILTER_MAX filters per socket, which unprivileged users can trigger repeatedly. Instead, patch 3 protects the receive lists where they are located: A spinlock in struct can_dev_rcv_lists protects the lists of a CAN device (initialized in can_set_ml_priv()) or the 'any' CAN device lists of a namespace (initialized in can_pernet_init()). can_rx_unregister() then finds the subscription independently of the current namespace of the device, which is kept accessible by the tracked device references the protocols already hold. The per-netns rcvlists_lock only protects the receive list statistics, for which CAN_RAW, CAN_BCM and CAN_ISOTP pass the namespace that has been used for can_rx_register(). As a consequence, CAN_RAW and CAN_ISOTP do not need RTNL in bind(), setsockopt() and release() anymore. bind() is serialized against NETDEV_UNREGISTER by publishing the device inside the RCU section of its lookup, as unlist_netdevice() and synchronize_net() precede NETDEV_UNREGISTER both on unregistration and on a namespace change. The notifiers recheck the binding under lock_sock(). As RTNL does not keep NETDEV_DOWN out of bind() anymore, bind() checks IFF_UP after publishing the bound device, with an smp_mb() pair against the notifiers, so a down transition during bind() is still reported with ENETDOWN. Link: https://patch.msgid.link/20261027-can-rx-lock-v12-0-eb5afc2d8b9b@hartkopp.net Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
commit
4937195745
|
|
@ -1040,17 +1040,21 @@ As described in :ref:`socketcan-receive-lists` the SocketCAN core uses several f
|
|||
lists to deliver received CAN frames to CAN protocol modules. These
|
||||
receive lists, their filters and the count of filter matches can be
|
||||
checked in the appropriate receive list. All entries contain the
|
||||
device and a protocol module identifier::
|
||||
CAN network device, the filter (can_id/can_mask), the number of filter
|
||||
matches, the inode number of the subscribing socket and the name of the
|
||||
receive function. A sock_inode of 0 denotes a kernel internal subscriber
|
||||
without a socket (e.g. CAN gateway or J1939). When the function name can
|
||||
not be resolved (e.g. with CONFIG_KALLSYMS=n) "(unknown)" is printed::
|
||||
|
||||
foo@bar:~$ cat /proc/net/can/rcvlist_all
|
||||
|
||||
receive list 'rx_all':
|
||||
(vcan3: no entry)
|
||||
(vcan2: no entry)
|
||||
(vcan1: no entry)
|
||||
device can_id can_mask function userdata matches ident
|
||||
vcan0 000 00000000 f88e6370 f6c6f400 0 raw
|
||||
(any: no entry)
|
||||
device can_id can_mask matches sock_inode function
|
||||
vcan0 000 00000000 0 63586 raw_rcv [can_raw]
|
||||
(vcan1: no entry)
|
||||
(vcan2: no entry)
|
||||
(vcan3: no entry)
|
||||
|
||||
In this example an application requests any CAN traffic from vcan0::
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
#include <linux/can.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
/* exposed CAN device capabilities for network layer */
|
||||
#define CAN_CAP_CC BIT(0) /* CAN CC aka Classical CAN */
|
||||
|
|
@ -59,6 +60,7 @@
|
|||
enum { RX_ERR, RX_ALL, RX_FIL, RX_INV, RX_MAX };
|
||||
|
||||
struct can_dev_rcv_lists {
|
||||
spinlock_t lock; /* protects the receive lists and entries */
|
||||
struct hlist_head rx[RX_MAX];
|
||||
struct hlist_head rx_sff[CAN_SFF_RCV_ARRAY_SZ];
|
||||
struct hlist_head rx_eff[CAN_EFF_RCV_ARRAY_SZ];
|
||||
|
|
@ -81,6 +83,7 @@ static inline struct can_ml_priv *can_get_ml_priv(struct net_device *dev)
|
|||
static inline void can_set_ml_priv(struct net_device *dev,
|
||||
struct can_ml_priv *ml_priv)
|
||||
{
|
||||
spin_lock_init(&ml_priv->dev_rcv_lists.lock);
|
||||
netdev_set_ml_priv(dev, ml_priv, ML_PRIV_CAN);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ extern void can_proto_unregister(const struct can_proto *cp);
|
|||
int can_rx_register(struct net *net, struct net_device *dev,
|
||||
canid_t can_id, canid_t mask,
|
||||
void (*func)(struct sk_buff *, void *),
|
||||
void *data, char *ident, struct sock *sk);
|
||||
void *data, u64 ino, struct sock *sk);
|
||||
|
||||
extern void can_rx_unregister(struct net *net, struct net_device *dev,
|
||||
canid_t can_id, canid_t mask,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ struct netns_can {
|
|||
|
||||
/* receive filters subscribed for 'all' CAN devices */
|
||||
struct can_dev_rcv_lists *rx_alldev_list;
|
||||
spinlock_t rcvlists_lock;
|
||||
spinlock_t rcvlists_lock; /* protects the rcv_entries(_max) stats */
|
||||
struct timer_list stattimer; /* timer for statistics update */
|
||||
struct can_pkg_stats *pkg_stats;
|
||||
struct can_rcv_lists_stats *rcv_lists_stats;
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ int can_send(struct sk_buff *skb, int loop)
|
|||
goto inval_skb;
|
||||
}
|
||||
|
||||
if (unlikely(skb->dev->type != ARPHRD_CAN)) {
|
||||
if (unlikely(!can_get_ml_priv(skb->dev))) {
|
||||
err = -EPERM;
|
||||
goto inval_skb;
|
||||
}
|
||||
|
|
@ -418,7 +418,7 @@ static struct hlist_head *can_rcv_list_find(canid_t *can_id, canid_t *mask,
|
|||
* @mask: CAN mask (see description)
|
||||
* @func: callback function on filter match
|
||||
* @data: returned parameter for callback function
|
||||
* @ident: string for calling module identification
|
||||
* @ino: inode number of sock (0 = unknown)
|
||||
* @sk: socket pointer (might be NULL)
|
||||
*
|
||||
* Description:
|
||||
|
|
@ -443,7 +443,7 @@ static struct hlist_head *can_rcv_list_find(canid_t *can_id, canid_t *mask,
|
|||
*/
|
||||
int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
|
||||
canid_t mask, void (*func)(struct sk_buff *, void *),
|
||||
void *data, char *ident, struct sock *sk)
|
||||
void *data, u64 ino, struct sock *sk)
|
||||
{
|
||||
struct receiver *rcv;
|
||||
struct hlist_head *rcv_list;
|
||||
|
|
@ -452,7 +452,7 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
|
|||
|
||||
/* insert new receiver (dev,canid,mask) -> (func,data) */
|
||||
|
||||
if (dev && (dev->type != ARPHRD_CAN || !can_get_ml_priv(dev)))
|
||||
if (dev && !can_get_ml_priv(dev))
|
||||
return -ENODEV;
|
||||
|
||||
if (dev && !net_eq(net, dev_net(dev)))
|
||||
|
|
@ -462,9 +462,10 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
|
|||
if (!rcv)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_bh(&net->can.rcvlists_lock);
|
||||
|
||||
dev_rcv_lists = can_dev_rcv_lists_find(net, dev);
|
||||
|
||||
spin_lock_bh(&dev_rcv_lists->lock);
|
||||
|
||||
rcv_list = can_rcv_list_find(&can_id, &mask, dev_rcv_lists);
|
||||
|
||||
rcv->can_id = can_id;
|
||||
|
|
@ -472,12 +473,15 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
|
|||
atomic_long_set(&rcv->matches, 0);
|
||||
rcv->func = func;
|
||||
rcv->data = data;
|
||||
rcv->ident = ident;
|
||||
rcv->ino = ino;
|
||||
rcv->sk = sk;
|
||||
|
||||
hlist_add_head_rcu(&rcv->list, rcv_list);
|
||||
dev_rcv_lists->entries++;
|
||||
|
||||
spin_unlock_bh(&dev_rcv_lists->lock);
|
||||
|
||||
spin_lock_bh(&net->can.rcvlists_lock);
|
||||
rcv_lists_stats->rcv_entries++;
|
||||
rcv_lists_stats->rcv_entries_max = max(rcv_lists_stats->rcv_entries_max,
|
||||
rcv_lists_stats->rcv_entries);
|
||||
|
|
@ -509,6 +513,14 @@ static void can_rx_delete_receiver(struct rcu_head *rp)
|
|||
*
|
||||
* Description:
|
||||
* Removes subscription entry depending on given (subscription) values.
|
||||
* The receive lists of a CAN device are protected by a per-device lock,
|
||||
* so the subscription is found even when the device has been moved to
|
||||
* another network namespace after can_rx_register(). The caller has to
|
||||
* ensure that @dev stays valid, e.g. by holding a reference to @dev or
|
||||
* RTNL. @net should be the namespace that has been used for
|
||||
* can_rx_register(). It selects the 'any' CAN device lists for
|
||||
* @dev == NULL and is otherwise only used for the receive list
|
||||
* statistics, which become imprecise when another namespace is passed.
|
||||
*/
|
||||
void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
|
||||
canid_t mask, void (*func)(struct sk_buff *, void *),
|
||||
|
|
@ -519,15 +531,13 @@ void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
|
|||
struct can_rcv_lists_stats *rcv_lists_stats = net->can.rcv_lists_stats;
|
||||
struct can_dev_rcv_lists *dev_rcv_lists;
|
||||
|
||||
if (dev && dev->type != ARPHRD_CAN)
|
||||
if (dev && !can_get_ml_priv(dev))
|
||||
return;
|
||||
|
||||
if (dev && !net_eq(net, dev_net(dev)))
|
||||
return;
|
||||
|
||||
spin_lock_bh(&net->can.rcvlists_lock);
|
||||
|
||||
dev_rcv_lists = can_dev_rcv_lists_find(net, dev);
|
||||
|
||||
spin_lock_bh(&dev_rcv_lists->lock);
|
||||
|
||||
rcv_list = can_rcv_list_find(&can_id, &mask, dev_rcv_lists);
|
||||
|
||||
/* Search the receiver list for the item to delete. This should
|
||||
|
|
@ -547,26 +557,26 @@ void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
|
|||
* a warning here.
|
||||
*/
|
||||
if (!rcv) {
|
||||
spin_unlock_bh(&dev_rcv_lists->lock);
|
||||
pr_warn("can: receive list entry not found for dev %s, id %03X, mask %03X\n",
|
||||
DNAME(dev), can_id, mask);
|
||||
goto out;
|
||||
return;
|
||||
}
|
||||
|
||||
hlist_del_rcu(&rcv->list);
|
||||
dev_rcv_lists->entries--;
|
||||
|
||||
spin_unlock_bh(&dev_rcv_lists->lock);
|
||||
|
||||
spin_lock_bh(&net->can.rcvlists_lock);
|
||||
if (rcv_lists_stats->rcv_entries > 0)
|
||||
rcv_lists_stats->rcv_entries--;
|
||||
|
||||
out:
|
||||
spin_unlock_bh(&net->can.rcvlists_lock);
|
||||
|
||||
/* schedule the receiver item for deletion */
|
||||
if (rcv) {
|
||||
if (rcv->sk)
|
||||
sock_hold(rcv->sk);
|
||||
call_rcu(&rcv->rcu, can_rx_delete_receiver);
|
||||
}
|
||||
if (rcv->sk)
|
||||
sock_hold(rcv->sk);
|
||||
call_rcu(&rcv->rcu, can_rx_delete_receiver);
|
||||
}
|
||||
EXPORT_SYMBOL(can_rx_unregister);
|
||||
|
||||
|
|
@ -684,8 +694,7 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev,
|
|||
{
|
||||
struct can_skb_ext *csx = can_skb_ext_find(skb);
|
||||
|
||||
if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) ||
|
||||
!csx || !can_is_can_skb(skb))) {
|
||||
if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_can_skb(skb))) {
|
||||
pr_warn_once("PF_CAN: dropped non conform CAN skbuff: dev type %d, len %d\n",
|
||||
dev->type, skb->len);
|
||||
|
||||
|
|
@ -710,8 +719,7 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev,
|
|||
{
|
||||
struct can_skb_ext *csx = can_skb_ext_find(skb);
|
||||
|
||||
if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) ||
|
||||
!csx || !can_is_canfd_skb(skb))) {
|
||||
if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_canfd_skb(skb))) {
|
||||
pr_warn_once("PF_CAN: dropped non conform CAN FD skbuff: dev type %d, len %d\n",
|
||||
dev->type, skb->len);
|
||||
|
||||
|
|
@ -736,8 +744,7 @@ static int canxl_rcv(struct sk_buff *skb, struct net_device *dev,
|
|||
{
|
||||
struct can_skb_ext *csx = can_skb_ext_find(skb);
|
||||
|
||||
if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) ||
|
||||
!csx || !can_is_canxl_skb(skb))) {
|
||||
if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_canxl_skb(skb))) {
|
||||
pr_warn_once("PF_CAN: dropped non conform CAN XL skbuff: dev type %d, len %d\n",
|
||||
dev->type, skb->len);
|
||||
|
||||
|
|
@ -826,6 +833,7 @@ static int can_pernet_init(struct net *net)
|
|||
net->can.rx_alldev_list = kzalloc_obj(*net->can.rx_alldev_list);
|
||||
if (!net->can.rx_alldev_list)
|
||||
goto out;
|
||||
spin_lock_init(&net->can.rx_alldev_list->lock);
|
||||
net->can.pkg_stats = kzalloc_obj(*net->can.pkg_stats);
|
||||
if (!net->can.pkg_stats)
|
||||
goto out_free_rx_alldev_list;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ struct receiver {
|
|||
atomic_long_t matches;
|
||||
void (*func)(struct sk_buff *skb, void *data);
|
||||
void *data;
|
||||
char *ident;
|
||||
u64 ino;
|
||||
struct sock *sk;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#include <linux/if_arp.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/can-ml.h>
|
||||
#include <linux/can/core.h>
|
||||
#include <linux/can/skb.h>
|
||||
#include <linux/can/bcm.h>
|
||||
|
|
@ -220,9 +221,7 @@ static int bcm_proc_show(struct seq_file *m, void *v)
|
|||
struct bcm_sock *bo = bcm_sk(sk);
|
||||
struct bcm_op *op;
|
||||
|
||||
seq_printf(m, ">>> socket %pK", sk->sk_socket);
|
||||
seq_printf(m, " / sk %pK", sk);
|
||||
seq_printf(m, " / bo %pK", bo);
|
||||
seq_printf(m, ">>> sock inode %s", bo->procname);
|
||||
seq_printf(m, " / dropped %lu", bo->dropped_usr_msgs);
|
||||
seq_printf(m, " / bound %s", bcm_proc_getifname(net, ifname, bo->ifindex));
|
||||
seq_printf(m, " <<<\n");
|
||||
|
|
@ -963,7 +962,7 @@ static void bcm_remove_op(struct bcm_op *op)
|
|||
static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
|
||||
{
|
||||
if (op->rx_reg_dev == dev) {
|
||||
can_rx_unregister(dev_net(dev), dev, op->can_id,
|
||||
can_rx_unregister(sock_net(op->sk), dev, op->can_id,
|
||||
REGMASK(op->can_id), bcm_rx_handler, op);
|
||||
|
||||
/* mark as removed subscription */
|
||||
|
|
@ -989,21 +988,10 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
|
|||
/* disable automatic timer on frame reception */
|
||||
op->flags |= RX_NO_AUTOTIMER;
|
||||
|
||||
/*
|
||||
* Don't care if we're bound or not (due to netdev
|
||||
* problems) can_rx_unregister() is always a save
|
||||
* thing to do here.
|
||||
*/
|
||||
if (op->ifindex) {
|
||||
/*
|
||||
* 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.
|
||||
/* Only remove subscriptions that had not been
|
||||
* removed in bcm_notify(). op->rx_reg_dev keeps
|
||||
* the device's receive lists accessible.
|
||||
*/
|
||||
if (op->rx_reg_dev)
|
||||
bcm_rx_unreg(op->rx_reg_dev, op);
|
||||
|
|
@ -1536,7 +1524,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
|
|||
op->can_id,
|
||||
REGMASK(op->can_id),
|
||||
bcm_rx_handler, op,
|
||||
"bcm", sk);
|
||||
sock_i_ino(sk), sk);
|
||||
|
||||
/* keep a tracked reference so that a later
|
||||
* unregister can safely reach the device even
|
||||
|
|
@ -1560,7 +1548,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
|
|||
} else {
|
||||
err = can_rx_register(sock_net(sk), NULL, op->can_id,
|
||||
REGMASK(op->can_id),
|
||||
bcm_rx_handler, op, "bcm", sk);
|
||||
bcm_rx_handler, op,
|
||||
sock_i_ino(sk), sk);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
|
|
@ -1719,7 +1708,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
|
|||
goto out_release;
|
||||
}
|
||||
|
||||
if (dev->type != ARPHRD_CAN) {
|
||||
if (!can_get_ml_priv(dev)) {
|
||||
dev_put(dev);
|
||||
ret = -ENODEV;
|
||||
goto out_release;
|
||||
|
|
@ -1867,7 +1856,7 @@ static int bcm_notifier(struct notifier_block *nb, unsigned long msg,
|
|||
{
|
||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
|
||||
if (dev->type != ARPHRD_CAN)
|
||||
if (!can_get_ml_priv(dev))
|
||||
return NOTIFY_DONE;
|
||||
if (msg != NETDEV_UNREGISTER && msg != NETDEV_DOWN)
|
||||
return NOTIFY_DONE;
|
||||
|
|
@ -1935,6 +1924,7 @@ static int bcm_release(struct socket *sock)
|
|||
list_del(&bo->notifier);
|
||||
spin_unlock(&bcm_notifier_lock);
|
||||
|
||||
/* bcm_notify() can not access this socket anymore */
|
||||
lock_sock(sk);
|
||||
|
||||
#if IS_ENABLED(CONFIG_PROC_FS)
|
||||
|
|
@ -1948,29 +1938,20 @@ static int bcm_release(struct socket *sock)
|
|||
bcm_remove_op(op);
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(op, next, &bo->rx_ops, list) {
|
||||
/*
|
||||
* Don't care if we're bound or not (due to netdev problems)
|
||||
* can_rx_unregister() is always a save thing to do here.
|
||||
*/
|
||||
list_for_each_entry(op, &bo->rx_ops, list) {
|
||||
if (op->ifindex) {
|
||||
/*
|
||||
* 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.
|
||||
/* Only remove subscriptions that had not been removed
|
||||
* in bcm_notify(). op->rx_reg_dev keeps the device's
|
||||
* receive lists accessible.
|
||||
*/
|
||||
if (op->rx_reg_dev)
|
||||
bcm_rx_unreg(op->rx_reg_dev, op);
|
||||
} else
|
||||
} else {
|
||||
can_rx_unregister(net, NULL, op->can_id,
|
||||
REGMASK(op->can_id),
|
||||
bcm_rx_handler, op);
|
||||
|
||||
}
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
synchronize_rcu();
|
||||
|
|
@ -2024,7 +2005,7 @@ static int bcm_connect(struct socket *sock, struct sockaddr_unsized *uaddr, int
|
|||
ret = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
if (dev->type != ARPHRD_CAN) {
|
||||
if (!can_get_ml_priv(dev)) {
|
||||
dev_put(dev);
|
||||
ret = -ENODEV;
|
||||
goto fail;
|
||||
|
|
@ -2040,8 +2021,9 @@ static int bcm_connect(struct socket *sock, struct sockaddr_unsized *uaddr, int
|
|||
|
||||
#if IS_ENABLED(CONFIG_PROC_FS)
|
||||
if (net->can.bcmproc_dir) {
|
||||
/* unique socket address as filename */
|
||||
sprintf(bo->procname, "%llu", sock_i_ino(sk));
|
||||
/* use unique socket inode number as filename */
|
||||
snprintf(bo->procname, sizeof(bo->procname),
|
||||
"%llu", sock_i_ino(sk));
|
||||
bo->bcm_proc_read = proc_create_net_single(bo->procname, 0644,
|
||||
net->can.bcmproc_dir,
|
||||
bcm_proc_show, sk);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@
|
|||
#include <linux/if_arp.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/can-ml.h>
|
||||
#include <linux/can/core.h>
|
||||
#include <linux/can/skb.h>
|
||||
#include <linux/can/gw.h>
|
||||
|
|
@ -579,7 +580,7 @@ static inline int cgw_register_filter(struct net *net, struct cgw_job *gwj)
|
|||
{
|
||||
return can_rx_register(net, gwj->src.dev, gwj->ccgw.filter.can_id,
|
||||
gwj->ccgw.filter.can_mask, can_can_gw_rcv,
|
||||
gwj, "gw", NULL);
|
||||
gwj, 0, NULL);
|
||||
}
|
||||
|
||||
static inline void cgw_unregister_filter(struct net *net, struct cgw_job *gwj)
|
||||
|
|
@ -612,7 +613,7 @@ static int cgw_notifier(struct notifier_block *nb,
|
|||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
struct net *net = dev_net(dev);
|
||||
|
||||
if (dev->type != ARPHRD_CAN)
|
||||
if (!can_get_ml_priv(dev))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
if (msg == NETDEV_UNREGISTER) {
|
||||
|
|
@ -1163,7 +1164,7 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||
if (!gwj->src.dev)
|
||||
goto out;
|
||||
|
||||
if (gwj->src.dev->type != ARPHRD_CAN)
|
||||
if (!can_get_ml_priv(gwj->src.dev))
|
||||
goto out;
|
||||
|
||||
gwj->dst.dev = __dev_get_by_index(net, gwj->ccgw.dst_idx);
|
||||
|
|
@ -1171,7 +1172,7 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||
if (!gwj->dst.dev)
|
||||
goto out;
|
||||
|
||||
if (gwj->dst.dev->type != ARPHRD_CAN)
|
||||
if (!can_get_ml_priv(gwj->dst.dev))
|
||||
goto out;
|
||||
|
||||
/* is sending the skb back to the incoming interface intended? */
|
||||
|
|
|
|||
228
net/can/isotp.c
228
net/can/isotp.c
|
|
@ -65,6 +65,7 @@
|
|||
#include <linux/if_arp.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/can-ml.h>
|
||||
#include <linux/can/core.h>
|
||||
#include <linux/can/skb.h>
|
||||
#include <linux/can/isotp.h>
|
||||
|
|
@ -1131,7 +1132,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
|
|||
int off;
|
||||
int err;
|
||||
|
||||
if (!so->bound || READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN)
|
||||
if (!READ_ONCE(so->bound) || READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
/* claim the socket under so->rx_lock: this serializes the claim
|
||||
|
|
@ -1176,7 +1177,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
|
|||
/* so->bound is only checked once above - a wakeup may have
|
||||
* unbound/rebound the socket meanwhile => recheck
|
||||
*/
|
||||
if (!so->bound) {
|
||||
if (!READ_ONCE(so->bound)) {
|
||||
err = -EADDRNOTAVAIL;
|
||||
goto err_out_drop;
|
||||
}
|
||||
|
|
@ -1422,7 +1423,7 @@ static int isotp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
|
|||
if (flags & ~(MSG_DONTWAIT | MSG_TRUNC | MSG_PEEK | MSG_CMSG_COMPAT))
|
||||
return -EINVAL;
|
||||
|
||||
if (!so->bound)
|
||||
if (!READ_ONCE(so->bound))
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
skb = skb_recv_datagram(sk, flags, &ret);
|
||||
|
|
@ -1496,11 +1497,11 @@ 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
|
||||
* tracked reference so->dev is taken at bind() time with rtnl_lock
|
||||
* the tracked reference so->dev keeps the device's receive lists
|
||||
* accessible, even after a namespace change
|
||||
*/
|
||||
if (so->bound && so->dev) {
|
||||
if (isotp_register_rxid(so))
|
||||
|
|
@ -1515,11 +1516,9 @@ static int isotp_release(struct socket *sock)
|
|||
}
|
||||
|
||||
so->ifindex = 0;
|
||||
so->bound = 0;
|
||||
WRITE_ONCE(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
|
||||
|
|
@ -1549,7 +1548,6 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
|
|||
struct sock *sk = sock->sk;
|
||||
struct isotp_sock *so = isotp_sk(sk);
|
||||
struct net *net = sock_net(sk);
|
||||
int ifindex;
|
||||
struct net_device *dev;
|
||||
canid_t tx_id = addr->can_addr.tp.tx_id;
|
||||
canid_t rx_id = addr->can_addr.tp.rx_id;
|
||||
|
|
@ -1587,7 +1585,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
|
|||
if (!addr->can_ifindex)
|
||||
return -ENODEV;
|
||||
|
||||
rtnl_lock();
|
||||
/* lock_sock() serializes the binding with isotp_notify() */
|
||||
lock_sock(sk);
|
||||
|
||||
if (so->bound) {
|
||||
|
|
@ -1613,61 +1611,83 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
|
|||
goto out;
|
||||
}
|
||||
|
||||
dev = dev_get_by_index(net, addr->can_ifindex);
|
||||
if (!dev) {
|
||||
/* Publish the device inside the RCU section of the lookup: When it
|
||||
* is unlisted concurrently, either isotp_notify() sees so->dev after
|
||||
* synchronize_net() or the lookup fails. Check the device before
|
||||
* publishing it.
|
||||
*/
|
||||
rcu_read_lock();
|
||||
dev = dev_get_by_index_rcu(net, addr->can_ifindex);
|
||||
if (!dev || !can_get_ml_priv(dev))
|
||||
err = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
if (dev->type != ARPHRD_CAN) {
|
||||
err = -ENODEV;
|
||||
goto out_put_dev;
|
||||
}
|
||||
if (READ_ONCE(dev->mtu) < so->ll.mtu) {
|
||||
else if (READ_ONCE(dev->mtu) < so->ll.mtu)
|
||||
err = -EINVAL;
|
||||
goto out_put_dev;
|
||||
if (!err) {
|
||||
dev_hold(dev);
|
||||
WRITE_ONCE(so->dev, dev);
|
||||
}
|
||||
if (!(dev->flags & IFF_UP))
|
||||
notify_enetdown = 1;
|
||||
rcu_read_unlock();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
ifindex = dev->ifindex;
|
||||
|
||||
if (isotp_register_rxid(so))
|
||||
can_rx_register(net, dev, rx_id, SINGLE_MASK(rx_id),
|
||||
isotp_rcv, sk, "isotp", sk);
|
||||
/* switch to new settings */
|
||||
so->ifindex = dev->ifindex;
|
||||
so->rxid = rx_id;
|
||||
so->txid = tx_id;
|
||||
|
||||
/* no consecutive frame echo skb in flight */
|
||||
WRITE_ONCE(so->cfecho, 0);
|
||||
|
||||
/* register for echo skb's */
|
||||
can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
|
||||
isotp_rcv_echo, sk, "isotpe", sk);
|
||||
/* register for echo skb's first (no echo traffic at this point) */
|
||||
err = can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
|
||||
isotp_rcv_echo, sk, sock_i_ino(sk), sk);
|
||||
if (err)
|
||||
goto out_put_dev;
|
||||
|
||||
/* switch to new settings */
|
||||
so->ifindex = ifindex;
|
||||
so->rxid = rx_id;
|
||||
so->txid = tx_id;
|
||||
so->bound = 1;
|
||||
if (isotp_register_rxid(so)) {
|
||||
err = can_rx_register(net, dev, rx_id, SINGLE_MASK(rx_id),
|
||||
isotp_rcv, sk, sock_i_ino(sk), sk);
|
||||
if (err) {
|
||||
/* unregister echo skb subscription */
|
||||
can_rx_unregister(net, dev, tx_id, SINGLE_MASK(tx_id),
|
||||
isotp_rcv_echo, sk);
|
||||
goto out_put_dev;
|
||||
}
|
||||
}
|
||||
|
||||
WRITE_ONCE(so->bound, 1);
|
||||
|
||||
/* bind() ok -> hold a reference for so->dev so that isotp_release()
|
||||
* can safely reach the device later, even if a concurrent
|
||||
* NETDEV_UNREGISTER has already unlisted it by ifindex.
|
||||
*/
|
||||
so->dev = dev;
|
||||
netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL);
|
||||
|
||||
/* Either isotp_notify() sees so->dev on NETDEV_DOWN or we see the
|
||||
* cleared IFF_UP, pairs with isotp_notify()
|
||||
*/
|
||||
smp_mb();
|
||||
if (!(READ_ONCE(dev->flags) & IFF_UP))
|
||||
notify_enetdown = 1;
|
||||
|
||||
out_put_dev:
|
||||
/* remove potential reference from dev_get_by_index() */
|
||||
/* revert so->ifindex and so->dev on failed bind() */
|
||||
if (err) {
|
||||
so->ifindex = 0;
|
||||
WRITE_ONCE(so->dev, NULL);
|
||||
}
|
||||
|
||||
/* remove reference from the device lookup */
|
||||
dev_put(dev);
|
||||
out:
|
||||
release_sock(sk);
|
||||
rtnl_unlock();
|
||||
|
||||
if (notify_enetdown) {
|
||||
sk->sk_err = ENETDOWN;
|
||||
if (!sock_flag(sk, SOCK_DEAD))
|
||||
sk_error_report(sk);
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -1689,8 +1709,8 @@ static int isotp_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
|
|||
return ISOTP_MIN_NAMELEN;
|
||||
}
|
||||
|
||||
static int isotp_setsockopt_locked(struct socket *sock, int level, int optname,
|
||||
sockptr_t optval, unsigned int optlen)
|
||||
static int isotp_setsockopt_locked(struct socket *sock, int optname,
|
||||
const void *val)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct isotp_sock *so = isotp_sk(sk);
|
||||
|
|
@ -1701,11 +1721,7 @@ static int isotp_setsockopt_locked(struct socket *sock, int level, int optname,
|
|||
|
||||
switch (optname) {
|
||||
case CAN_ISOTP_OPTS:
|
||||
if (optlen != sizeof(struct can_isotp_options))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&so->opt, optval, optlen))
|
||||
return -EFAULT;
|
||||
memcpy(&so->opt, val, sizeof(so->opt));
|
||||
|
||||
/* no separate rx_ext_address is given => use ext_address */
|
||||
if (!(so->opt.flags & CAN_ISOTP_RX_EXT_ADDR))
|
||||
|
|
@ -1730,55 +1746,37 @@ static int isotp_setsockopt_locked(struct socket *sock, int level, int optname,
|
|||
break;
|
||||
|
||||
case CAN_ISOTP_RECV_FC:
|
||||
if (optlen != sizeof(struct can_isotp_fc_options))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&so->rxfc, optval, optlen))
|
||||
return -EFAULT;
|
||||
memcpy(&so->rxfc, val, sizeof(so->rxfc));
|
||||
break;
|
||||
|
||||
case CAN_ISOTP_TX_STMIN:
|
||||
if (optlen != sizeof(u32))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&so->force_tx_stmin, optval, optlen))
|
||||
return -EFAULT;
|
||||
so->force_tx_stmin = *(const u32 *)val;
|
||||
break;
|
||||
|
||||
case CAN_ISOTP_RX_STMIN:
|
||||
if (optlen != sizeof(u32))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&so->force_rx_stmin, optval, optlen))
|
||||
return -EFAULT;
|
||||
so->force_rx_stmin = *(const u32 *)val;
|
||||
break;
|
||||
|
||||
case CAN_ISOTP_LL_OPTS:
|
||||
if (optlen == sizeof(struct can_isotp_ll_options)) {
|
||||
struct can_isotp_ll_options ll;
|
||||
case CAN_ISOTP_LL_OPTS: {
|
||||
const struct can_isotp_ll_options *ll = val;
|
||||
|
||||
if (copy_from_sockptr(&ll, optval, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
/* check for correct ISO 11898-1 DLC data length */
|
||||
if (ll.tx_dl != padlen(ll.tx_dl))
|
||||
return -EINVAL;
|
||||
|
||||
if (ll.mtu != CAN_MTU && ll.mtu != CANFD_MTU)
|
||||
return -EINVAL;
|
||||
|
||||
if (ll.mtu == CAN_MTU &&
|
||||
(ll.tx_dl > CAN_MAX_DLEN || ll.tx_flags != 0))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(&so->ll, &ll, sizeof(ll));
|
||||
|
||||
/* set ll_dl for tx path to similar place as for rx */
|
||||
so->tx.ll_dl = ll.tx_dl;
|
||||
} else {
|
||||
/* check for correct ISO 11898-1 DLC data length */
|
||||
if (ll->tx_dl != padlen(ll->tx_dl))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ll->mtu != CAN_MTU && ll->mtu != CANFD_MTU)
|
||||
return -EINVAL;
|
||||
|
||||
if (ll->mtu == CAN_MTU &&
|
||||
(ll->tx_dl > CAN_MAX_DLEN || ll->tx_flags != 0))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(&so->ll, ll, sizeof(*ll));
|
||||
|
||||
/* set ll_dl for tx path to similar place as for rx */
|
||||
so->tx.ll_dl = ll->tx_dl;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ret = -ENOPROTOOPT;
|
||||
|
|
@ -1792,13 +1790,49 @@ static int isotp_setsockopt(struct socket *sock, int level, int optname,
|
|||
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
union {
|
||||
struct can_isotp_options opt;
|
||||
struct can_isotp_fc_options rxfc;
|
||||
struct can_isotp_ll_options ll;
|
||||
u32 stmin;
|
||||
} val;
|
||||
unsigned int size;
|
||||
int ret;
|
||||
|
||||
if (level != SOL_CAN_ISOTP)
|
||||
return -EINVAL;
|
||||
|
||||
/* copy the option values from user space before lock_sock() */
|
||||
switch (optname) {
|
||||
case CAN_ISOTP_OPTS:
|
||||
size = sizeof(val.opt);
|
||||
break;
|
||||
|
||||
case CAN_ISOTP_RECV_FC:
|
||||
size = sizeof(val.rxfc);
|
||||
break;
|
||||
|
||||
case CAN_ISOTP_TX_STMIN:
|
||||
case CAN_ISOTP_RX_STMIN:
|
||||
size = sizeof(val.stmin);
|
||||
break;
|
||||
|
||||
case CAN_ISOTP_LL_OPTS:
|
||||
size = sizeof(val.ll);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
|
||||
if (optlen != size)
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&val, optval, size))
|
||||
return -EFAULT;
|
||||
|
||||
lock_sock(sk);
|
||||
ret = isotp_setsockopt_locked(sock, level, optname, optval, optlen);
|
||||
ret = isotp_setsockopt_locked(sock, optname, &val);
|
||||
release_sock(sk);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1863,12 +1897,21 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
|
|||
if (!net_eq(dev_net(dev), sock_net(sk)))
|
||||
return;
|
||||
|
||||
if (so->dev != dev)
|
||||
if (msg == NETDEV_DOWN)
|
||||
smp_mb(); /* pairs with isotp_bind(), see raw_notify() */
|
||||
|
||||
/* lockless fast path, see raw_notify() */
|
||||
if (READ_ONCE(so->dev) != dev)
|
||||
return;
|
||||
|
||||
switch (msg) {
|
||||
case NETDEV_UNREGISTER:
|
||||
lock_sock(sk);
|
||||
if (so->dev != dev) {
|
||||
release_sock(sk);
|
||||
break;
|
||||
}
|
||||
|
||||
/* remove current filters & unregister */
|
||||
if (so->bound) {
|
||||
if (isotp_register_rxid(so))
|
||||
|
|
@ -1883,16 +1926,19 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
|
|||
}
|
||||
|
||||
so->ifindex = 0;
|
||||
so->bound = 0;
|
||||
so->dev = NULL;
|
||||
release_sock(sk);
|
||||
WRITE_ONCE(so->bound, 0);
|
||||
WRITE_ONCE(so->dev, NULL);
|
||||
|
||||
sk->sk_err = ENODEV;
|
||||
if (!sock_flag(sk, SOCK_DEAD))
|
||||
sk_error_report(sk);
|
||||
release_sock(sk);
|
||||
break;
|
||||
|
||||
case NETDEV_DOWN:
|
||||
if (!READ_ONCE(so->bound))
|
||||
break;
|
||||
|
||||
sk->sk_err = ENETDOWN;
|
||||
if (!sock_flag(sk, SOCK_DEAD))
|
||||
sk_error_report(sk);
|
||||
|
|
@ -1905,7 +1951,7 @@ static int isotp_notifier(struct notifier_block *nb, unsigned long msg,
|
|||
{
|
||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
|
||||
if (dev->type != ARPHRD_CAN)
|
||||
if (!can_get_ml_priv(dev))
|
||||
return NOTIFY_DONE;
|
||||
if (msg != NETDEV_UNREGISTER && msg != NETDEV_DOWN)
|
||||
return NOTIFY_DONE;
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ static int j1939_can_rx_register(struct j1939_priv *priv)
|
|||
|
||||
j1939_priv_get(priv);
|
||||
ret = can_rx_register(dev_net(ndev), ndev, J1939_CAN_ID, J1939_CAN_MASK,
|
||||
j1939_can_recv, priv, "j1939", NULL);
|
||||
j1939_can_recv, priv, 0, NULL);
|
||||
if (ret < 0) {
|
||||
j1939_priv_put(priv);
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -76,16 +76,39 @@ static const char rx_list_name[][8] = {
|
|||
* af_can statistics stuff
|
||||
*/
|
||||
|
||||
static void can_reset_pkg_stats(struct can_pkg_stats *pkg_stats)
|
||||
{
|
||||
atomic_long_set(&pkg_stats->rx_frames, 0);
|
||||
atomic_long_set(&pkg_stats->tx_frames, 0);
|
||||
atomic_long_set(&pkg_stats->matches, 0);
|
||||
|
||||
pkg_stats->total_rx_rate = 0;
|
||||
pkg_stats->total_tx_rate = 0;
|
||||
pkg_stats->total_rx_match_ratio = 0;
|
||||
|
||||
pkg_stats->current_rx_rate = 0;
|
||||
pkg_stats->current_tx_rate = 0;
|
||||
pkg_stats->current_rx_match_ratio = 0;
|
||||
|
||||
pkg_stats->max_rx_rate = 0;
|
||||
pkg_stats->max_tx_rate = 0;
|
||||
pkg_stats->max_rx_match_ratio = 0;
|
||||
|
||||
atomic_long_set(&pkg_stats->rx_frames_delta, 0);
|
||||
atomic_long_set(&pkg_stats->tx_frames_delta, 0);
|
||||
atomic_long_set(&pkg_stats->matches_delta, 0);
|
||||
}
|
||||
|
||||
static void can_init_stats(struct net *net)
|
||||
{
|
||||
struct can_pkg_stats *pkg_stats = net->can.pkg_stats;
|
||||
struct can_rcv_lists_stats *rcv_lists_stats = net->can.rcv_lists_stats;
|
||||
/*
|
||||
* This memset function is called from a timer context (when
|
||||
* This stats reset is called from a timer context (when
|
||||
* can_stattimer is active which is the default) OR in a process
|
||||
* context (reading the proc_fs when can_stattimer is disabled).
|
||||
*/
|
||||
memset(pkg_stats, 0, sizeof(struct can_pkg_stats));
|
||||
can_reset_pkg_stats(pkg_stats);
|
||||
pkg_stats->jiffies_init = jiffies;
|
||||
|
||||
rcv_lists_stats->stats_reset++;
|
||||
|
|
@ -121,29 +144,23 @@ void can_stat_update(struct timer_list *t)
|
|||
long rx_frames = atomic_long_read(&pkg_stats->rx_frames);
|
||||
long tx_frames = atomic_long_read(&pkg_stats->tx_frames);
|
||||
long matches = atomic_long_read(&pkg_stats->matches);
|
||||
long rx_frames_delta = atomic_long_read(&pkg_stats->rx_frames_delta);
|
||||
long tx_frames_delta = atomic_long_read(&pkg_stats->tx_frames_delta);
|
||||
long matches_delta = atomic_long_read(&pkg_stats->matches_delta);
|
||||
/* fetch and clear values for 'current rate' calculation */
|
||||
long rx_frames_delta = atomic_long_xchg(&pkg_stats->rx_frames_delta, 0);
|
||||
long tx_frames_delta = atomic_long_xchg(&pkg_stats->tx_frames_delta, 0);
|
||||
long matches_delta = atomic_long_xchg(&pkg_stats->matches_delta, 0);
|
||||
|
||||
/* restart counting in timer context on user request */
|
||||
if (user_reset)
|
||||
can_init_stats(net);
|
||||
|
||||
/* restart counting on jiffies overflow */
|
||||
if (j < pkg_stats->jiffies_init)
|
||||
can_init_stats(net);
|
||||
|
||||
/* prevent overflow in calc_rate() */
|
||||
if (rx_frames > (LONG_MAX / HZ))
|
||||
can_init_stats(net);
|
||||
|
||||
/* prevent overflow in calc_rate() */
|
||||
if (tx_frames > (LONG_MAX / HZ))
|
||||
can_init_stats(net);
|
||||
|
||||
/* matches overflow - very improbable */
|
||||
if (matches > (LONG_MAX / 100))
|
||||
/*
|
||||
* Restart counting in timer context on user request, on jiffies
|
||||
* overflow or to prevent overflows in calc_rate() and in the
|
||||
* match ratio calculation. The local snapshots are stale after
|
||||
* the reset, so skip the calculation for this interval.
|
||||
*/
|
||||
if (user_reset || j < pkg_stats->jiffies_init ||
|
||||
rx_frames > (LONG_MAX / HZ) || tx_frames > (LONG_MAX / HZ) ||
|
||||
matches > (LONG_MAX / 100)) {
|
||||
can_init_stats(net);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* calc total values */
|
||||
if (rx_frames)
|
||||
|
|
@ -172,11 +189,7 @@ void can_stat_update(struct timer_list *t)
|
|||
if (pkg_stats->max_rx_match_ratio < pkg_stats->current_rx_match_ratio)
|
||||
pkg_stats->max_rx_match_ratio = pkg_stats->current_rx_match_ratio;
|
||||
|
||||
/* clear values for 'current rate' calculation */
|
||||
atomic_long_set(&pkg_stats->tx_frames_delta, 0);
|
||||
atomic_long_set(&pkg_stats->rx_frames_delta, 0);
|
||||
atomic_long_set(&pkg_stats->matches_delta, 0);
|
||||
|
||||
out:
|
||||
/* restart timer (one second) */
|
||||
mod_timer(&net->can.stattimer, round_jiffies(jiffies + HZ));
|
||||
}
|
||||
|
|
@ -191,26 +204,32 @@ static void can_print_rcvlist(struct seq_file *m, struct hlist_head *rx_list,
|
|||
struct receiver *r;
|
||||
|
||||
hlist_for_each_entry_rcu(r, rx_list, list) {
|
||||
char symcheck[4];
|
||||
char *fmt = (r->can_id & CAN_EFF_FLAG)?
|
||||
" %-5s %08x %08x %pK %pK %8ld %s\n" :
|
||||
" %-5s %03x %08x %pK %pK %8ld %s\n";
|
||||
" %6s %08x %08x %8ld %10llu" :
|
||||
" %6s %03x %08x %8ld %10llu";
|
||||
|
||||
seq_printf(m, fmt, DNAME(dev), r->can_id, r->mask,
|
||||
r->func, r->data, atomic_long_read(&r->matches),
|
||||
r->ident);
|
||||
atomic_long_read(&r->matches), r->ino);
|
||||
|
||||
/* %ps falls back to the raw text address when the symbol
|
||||
* can not be resolved (KALLSYMS=n, stripped module symbols)
|
||||
*/
|
||||
snprintf(symcheck, sizeof(symcheck), "%ps", r->func);
|
||||
if (strncmp(symcheck, "0x", 2))
|
||||
seq_printf(m, " %ps\n", r->func);
|
||||
else
|
||||
seq_puts(m, " (unknown)\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void can_print_recv_banner(struct seq_file *m)
|
||||
{
|
||||
/*
|
||||
* can1. 00000000 00000000 00000000
|
||||
* ....... 0 tp20
|
||||
* device can_id can_mask matches sock_inode function
|
||||
* vcan0 000 00000000 0 43798 raw_rcv [can_raw]
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_64BIT))
|
||||
seq_puts(m, " device can_id can_mask function userdata matches ident\n");
|
||||
else
|
||||
seq_puts(m, " device can_id can_mask function userdata matches ident\n");
|
||||
seq_puts(m, " device can_id can_mask matches sock_inode function\n");
|
||||
}
|
||||
|
||||
static int can_stats_proc_show(struct seq_file *m, void *v)
|
||||
|
|
|
|||
278
net/can/raw.c
278
net/can/raw.c
|
|
@ -85,6 +85,7 @@ struct raw_sock {
|
|||
struct sock sk;
|
||||
struct net_device *dev;
|
||||
netdevice_tracker dev_tracker;
|
||||
struct net_device *bind_dev; /* hint for raw_notify(), no reference */
|
||||
struct list_head notifier;
|
||||
int ifindex;
|
||||
unsigned int bound:1;
|
||||
|
|
@ -224,9 +225,10 @@ static int raw_enable_filters(struct net *net, struct net_device *dev,
|
|||
int i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
cond_resched();
|
||||
err = can_rx_register(net, dev, filter[i].can_id,
|
||||
filter[i].can_mask,
|
||||
raw_rcv, sk, "raw", sk);
|
||||
raw_rcv, sk, sock_i_ino(sk), sk);
|
||||
if (err) {
|
||||
/* clean up successfully registered filters */
|
||||
while (--i >= 0)
|
||||
|
|
@ -247,7 +249,7 @@ static int raw_enable_errfilter(struct net *net, struct net_device *dev,
|
|||
|
||||
if (err_mask)
|
||||
err = can_rx_register(net, dev, 0, err_mask | CAN_ERR_FLAG,
|
||||
raw_rcv, sk, "raw", sk);
|
||||
raw_rcv, sk, sock_i_ino(sk), sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
@ -258,9 +260,11 @@ static void raw_disable_filters(struct net *net, struct net_device *dev,
|
|||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
for (i = 0; i < count; i++) {
|
||||
can_rx_unregister(net, dev, filter[i].can_id,
|
||||
filter[i].can_mask, raw_rcv, sk);
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void raw_disable_errfilter(struct net *net,
|
||||
|
|
@ -305,16 +309,31 @@ static void raw_notify(struct raw_sock *ro, unsigned long msg,
|
|||
struct net_device *dev)
|
||||
{
|
||||
struct sock *sk = &ro->sk;
|
||||
struct net_device *bind_dev;
|
||||
|
||||
if (!net_eq(dev_net(dev), sock_net(sk)))
|
||||
return;
|
||||
|
||||
if (ro->dev != dev)
|
||||
if (msg == NETDEV_DOWN)
|
||||
smp_mb(); /* IFF_UP is cleared before, pairs with raw_bind() */
|
||||
|
||||
/* Lockless fast path, ro->bind_dev covers a concurrent bind().
|
||||
* Read it before ro->dev, pairs with smp_store_release() in
|
||||
* raw_bind(). A stale ro->bind_dev only costs a lock_sock().
|
||||
*/
|
||||
bind_dev = smp_load_acquire(&ro->bind_dev);
|
||||
if (READ_ONCE(ro->dev) != dev &&
|
||||
(msg != NETDEV_UNREGISTER || bind_dev != dev))
|
||||
return;
|
||||
|
||||
switch (msg) {
|
||||
case NETDEV_UNREGISTER:
|
||||
lock_sock(sk);
|
||||
if (ro->dev != dev) {
|
||||
release_sock(sk);
|
||||
break;
|
||||
}
|
||||
|
||||
/* remove current filters & unregister */
|
||||
if (ro->bound) {
|
||||
raw_disable_allfilters(dev_net(dev), dev, sk);
|
||||
|
|
@ -326,13 +345,13 @@ static void raw_notify(struct raw_sock *ro, unsigned long msg,
|
|||
|
||||
ro->ifindex = 0;
|
||||
ro->bound = 0;
|
||||
ro->dev = NULL;
|
||||
WRITE_ONCE(ro->dev, NULL);
|
||||
ro->count = 0;
|
||||
release_sock(sk);
|
||||
|
||||
sk->sk_err = ENODEV;
|
||||
if (!sock_flag(sk, SOCK_DEAD))
|
||||
sk_error_report(sk);
|
||||
release_sock(sk);
|
||||
break;
|
||||
|
||||
case NETDEV_DOWN:
|
||||
|
|
@ -348,7 +367,7 @@ static int raw_notifier(struct notifier_block *nb, unsigned long msg,
|
|||
{
|
||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
|
||||
if (dev->type != ARPHRD_CAN)
|
||||
if (!can_get_ml_priv(dev))
|
||||
return NOTIFY_DONE;
|
||||
if (msg != NETDEV_UNREGISTER && msg != NETDEV_DOWN)
|
||||
return NOTIFY_DONE;
|
||||
|
|
@ -381,6 +400,7 @@ static int raw_init(struct sock *sk)
|
|||
ro->bound = 0;
|
||||
ro->ifindex = 0;
|
||||
ro->dev = NULL;
|
||||
ro->bind_dev = NULL;
|
||||
|
||||
/* set default filter to single entry dfilter */
|
||||
ro->dfilter.can_id = 0;
|
||||
|
|
@ -431,13 +451,15 @@ static int raw_release(struct socket *sock)
|
|||
list_del(&ro->notifier);
|
||||
spin_unlock(&raw_notifier_lock);
|
||||
|
||||
rtnl_lock();
|
||||
lock_sock(sk);
|
||||
|
||||
/* remove current filters & unregister */
|
||||
/* remove current filters & unregister
|
||||
* the tracked reference ro->dev keeps the device's receive lists
|
||||
* accessible, even after a namespace change
|
||||
*/
|
||||
if (ro->bound) {
|
||||
if (ro->dev) {
|
||||
raw_disable_allfilters(dev_net(ro->dev), ro->dev, sk);
|
||||
raw_disable_allfilters(net, ro->dev, sk);
|
||||
netdev_put(ro->dev, &ro->dev_tracker);
|
||||
} else {
|
||||
raw_disable_allfilters(net, NULL, sk);
|
||||
|
|
@ -456,7 +478,6 @@ static int raw_release(struct socket *sock)
|
|||
sock->sk = NULL;
|
||||
|
||||
release_sock(sk);
|
||||
rtnl_unlock();
|
||||
|
||||
sock_prot_inuse_add(net, sk->sk_prot, -1);
|
||||
sock_put(sk);
|
||||
|
|
@ -479,26 +500,34 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
|
|||
if (addr->can_family != AF_CAN)
|
||||
return -EINVAL;
|
||||
|
||||
rtnl_lock();
|
||||
/* lock_sock() serializes the binding with raw_notify() */
|
||||
lock_sock(sk);
|
||||
|
||||
if (ro->bound && addr->can_ifindex == ro->ifindex)
|
||||
goto out;
|
||||
|
||||
if (addr->can_ifindex) {
|
||||
dev = dev_get_by_index(sock_net(sk), addr->can_ifindex);
|
||||
/* Publish the device inside the RCU section of the lookup:
|
||||
* When it is unlisted concurrently, either raw_notify() sees
|
||||
* ro->bind_dev after synchronize_net() or the lookup fails.
|
||||
*/
|
||||
rcu_read_lock();
|
||||
dev = dev_get_by_index_rcu(sock_net(sk), addr->can_ifindex);
|
||||
if (dev) {
|
||||
dev_hold(dev);
|
||||
/* pairs with smp_load_acquire() in raw_notify() */
|
||||
smp_store_release(&ro->bind_dev, dev);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
if (!dev) {
|
||||
err = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
if (dev->type != ARPHRD_CAN) {
|
||||
if (!can_get_ml_priv(dev)) {
|
||||
err = -ENODEV;
|
||||
goto out_put_dev;
|
||||
}
|
||||
|
||||
if (!(dev->flags & IFF_UP))
|
||||
notify_enetdown = 1;
|
||||
|
||||
ifindex = dev->ifindex;
|
||||
|
||||
/* filters set by default/setsockopt */
|
||||
|
|
@ -517,7 +546,7 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
|
|||
if (ro->bound) {
|
||||
/* unregister old filters */
|
||||
if (ro->dev) {
|
||||
raw_disable_allfilters(dev_net(ro->dev),
|
||||
raw_disable_allfilters(sock_net(sk),
|
||||
ro->dev, sk);
|
||||
/* drop reference to old ro->dev */
|
||||
netdev_put(ro->dev, &ro->dev_tracker);
|
||||
|
|
@ -528,24 +557,32 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
|
|||
ro->ifindex = ifindex;
|
||||
ro->bound = 1;
|
||||
/* bind() ok -> hold a reference for new ro->dev */
|
||||
ro->dev = dev;
|
||||
WRITE_ONCE(ro->dev, dev);
|
||||
if (ro->dev)
|
||||
netdev_hold(ro->dev, &ro->dev_tracker, GFP_KERNEL);
|
||||
|
||||
if (dev) {
|
||||
/* Either raw_notify() sees ro->dev on NETDEV_DOWN or
|
||||
* we see the cleared IFF_UP, pairs with raw_notify()
|
||||
*/
|
||||
smp_mb();
|
||||
if (!(READ_ONCE(dev->flags) & IFF_UP))
|
||||
notify_enetdown = 1;
|
||||
}
|
||||
}
|
||||
|
||||
out_put_dev:
|
||||
/* remove potential reference from dev_get_by_index() */
|
||||
/* remove reference from the device lookup */
|
||||
dev_put(dev);
|
||||
out:
|
||||
release_sock(sk);
|
||||
rtnl_unlock();
|
||||
|
||||
if (notify_enetdown) {
|
||||
sk->sk_err = ENETDOWN;
|
||||
if (!sock_flag(sk, SOCK_DEAD))
|
||||
sk_error_report(sk);
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -567,58 +604,36 @@ static int raw_getname(struct socket *sock, struct sockaddr *uaddr,
|
|||
}
|
||||
|
||||
static int raw_setsockopt_locked(struct socket *sock, int optname,
|
||||
sockptr_t optval, unsigned int optlen)
|
||||
const void *val, struct can_filter *filter,
|
||||
int count)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct raw_sock *ro = raw_sk(sk);
|
||||
struct can_filter *filter = NULL; /* dyn. alloc'ed filters */
|
||||
struct can_filter sfilter; /* single filter */
|
||||
struct net_device *dev = NULL;
|
||||
can_err_mask_t err_mask = 0;
|
||||
int count = 0;
|
||||
can_err_mask_t err_mask;
|
||||
int flag;
|
||||
int err = 0;
|
||||
|
||||
switch (optname) {
|
||||
case CAN_RAW_FILTER:
|
||||
if (optlen % sizeof(struct can_filter) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (optlen > CAN_RAW_FILTER_MAX * sizeof(struct can_filter))
|
||||
return -EINVAL;
|
||||
|
||||
count = optlen / sizeof(struct can_filter);
|
||||
|
||||
if (count > 1) {
|
||||
/* filter does not fit into dfilter => alloc space */
|
||||
filter = memdup_sockptr(optval, optlen);
|
||||
if (IS_ERR(filter))
|
||||
return PTR_ERR(filter);
|
||||
} else if (count == 1) {
|
||||
if (copy_from_sockptr(&sfilter, optval, sizeof(sfilter)))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* @filter has been copied by raw_setsockopt(). It is linked
|
||||
* to the socket on success and freed by the caller on error.
|
||||
*
|
||||
* The reg_state check is only a shortcut: On a concurrent
|
||||
* unregistration or namespace change raw_notify() waits for
|
||||
* lock_sock() and removes the filters set here.
|
||||
*/
|
||||
dev = ro->dev;
|
||||
if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED) {
|
||||
if (count > 1)
|
||||
kfree(filter);
|
||||
if (ro->bound && dev &&
|
||||
READ_ONCE(dev->reg_state) != NETREG_REGISTERED)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (ro->bound) {
|
||||
/* (try to) register the new filters */
|
||||
if (count == 1)
|
||||
err = raw_enable_filters(sock_net(sk), dev, sk,
|
||||
&sfilter, 1);
|
||||
else
|
||||
err = raw_enable_filters(sock_net(sk), dev, sk,
|
||||
filter, count);
|
||||
if (err) {
|
||||
if (count > 1)
|
||||
kfree(filter);
|
||||
err = raw_enable_filters(sock_net(sk), dev, sk, filter,
|
||||
count);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* remove old filter registrations */
|
||||
raw_disable_filters(sock_net(sk), dev, sk, ro->filter,
|
||||
|
|
@ -632,7 +647,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
|
|||
/* link new filters to the socket */
|
||||
if (count == 1) {
|
||||
/* copy filter data for single filter */
|
||||
ro->dfilter = sfilter;
|
||||
ro->dfilter = *filter;
|
||||
filter = &ro->dfilter;
|
||||
}
|
||||
ro->filter = filter;
|
||||
|
|
@ -640,16 +655,12 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
|
|||
break;
|
||||
|
||||
case CAN_RAW_ERR_FILTER:
|
||||
if (optlen != sizeof(err_mask))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&err_mask, optval, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
err_mask &= CAN_ERR_MASK;
|
||||
err_mask = *(const can_err_mask_t *)val & CAN_ERR_MASK;
|
||||
|
||||
/* lockless shortcut, see CAN_RAW_FILTER */
|
||||
dev = ro->dev;
|
||||
if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED)
|
||||
if (ro->bound && dev &&
|
||||
READ_ONCE(dev->reg_state) != NETREG_REGISTERED)
|
||||
return -ENODEV;
|
||||
|
||||
/* remove current error mask */
|
||||
|
|
@ -671,31 +682,15 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
|
|||
break;
|
||||
|
||||
case CAN_RAW_LOOPBACK:
|
||||
if (optlen != sizeof(flag))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&flag, optval, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
ro->loopback = !!flag;
|
||||
ro->loopback = !!*(const int *)val;
|
||||
break;
|
||||
|
||||
case CAN_RAW_RECV_OWN_MSGS:
|
||||
if (optlen != sizeof(flag))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&flag, optval, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
ro->recv_own_msgs = !!flag;
|
||||
ro->recv_own_msgs = !!*(const int *)val;
|
||||
break;
|
||||
|
||||
case CAN_RAW_FD_FRAMES:
|
||||
if (optlen != sizeof(flag))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&flag, optval, optlen))
|
||||
return -EFAULT;
|
||||
flag = *(const int *)val;
|
||||
|
||||
/* Enabling CAN XL includes CAN FD */
|
||||
if (ro->xl_frames && !flag)
|
||||
|
|
@ -705,13 +700,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
|
|||
break;
|
||||
|
||||
case CAN_RAW_XL_FRAMES:
|
||||
if (optlen != sizeof(flag))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&flag, optval, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
ro->xl_frames = !!flag;
|
||||
ro->xl_frames = !!*(const int *)val;
|
||||
|
||||
/* Enabling CAN XL includes CAN FD */
|
||||
if (ro->xl_frames)
|
||||
|
|
@ -719,11 +708,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
|
|||
break;
|
||||
|
||||
case CAN_RAW_XL_VCID_OPTS:
|
||||
if (optlen != sizeof(ro->raw_vcid_opts))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&ro->raw_vcid_opts, optval, optlen))
|
||||
return -EFAULT;
|
||||
memcpy(&ro->raw_vcid_opts, val, sizeof(ro->raw_vcid_opts));
|
||||
|
||||
/* prepare 32 bit values for handling in hot path */
|
||||
ro->tx_vcid_shifted = ro->raw_vcid_opts.tx_vcid << CANXL_VCID_OFFSET;
|
||||
|
|
@ -732,13 +717,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
|
|||
break;
|
||||
|
||||
case CAN_RAW_JOIN_FILTERS:
|
||||
if (optlen != sizeof(flag))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&flag, optval, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
ro->join_filters = !!flag;
|
||||
ro->join_filters = !!*(const int *)val;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -751,18 +730,83 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
|
|||
sockptr_t optval, unsigned int optlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct can_filter *filter = NULL; /* dyn. alloc'ed filters */
|
||||
struct can_filter sfilter; /* single filter */
|
||||
union {
|
||||
can_err_mask_t err_mask;
|
||||
int flag;
|
||||
struct can_raw_vcid_options vcid_opts;
|
||||
} val;
|
||||
unsigned int size = 0;
|
||||
int count = 0;
|
||||
int err;
|
||||
|
||||
if (level != SOL_CAN_RAW)
|
||||
return -EINVAL;
|
||||
|
||||
rtnl_lock();
|
||||
/* Copy the option values from user space before lock_sock(), so a
|
||||
* stalled copy can not block raw_notify() waiting for lock_sock()
|
||||
* under RTNL.
|
||||
*/
|
||||
switch (optname) {
|
||||
case CAN_RAW_FILTER:
|
||||
if (optlen % sizeof(struct can_filter) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (optlen > CAN_RAW_FILTER_MAX * sizeof(struct can_filter))
|
||||
return -EINVAL;
|
||||
|
||||
count = optlen / sizeof(struct can_filter);
|
||||
|
||||
if (count > 1) {
|
||||
/* filter does not fit into dfilter => alloc space */
|
||||
filter = memdup_sockptr(optval, optlen);
|
||||
if (IS_ERR(filter))
|
||||
return PTR_ERR(filter);
|
||||
} else if (count == 1) {
|
||||
if (copy_from_sockptr(&sfilter, optval, sizeof(sfilter)))
|
||||
return -EFAULT;
|
||||
filter = &sfilter;
|
||||
}
|
||||
break;
|
||||
|
||||
case CAN_RAW_ERR_FILTER:
|
||||
size = sizeof(val.err_mask);
|
||||
break;
|
||||
|
||||
case CAN_RAW_XL_VCID_OPTS:
|
||||
size = sizeof(val.vcid_opts);
|
||||
break;
|
||||
|
||||
case CAN_RAW_LOOPBACK:
|
||||
case CAN_RAW_RECV_OWN_MSGS:
|
||||
case CAN_RAW_FD_FRAMES:
|
||||
case CAN_RAW_XL_FRAMES:
|
||||
case CAN_RAW_JOIN_FILTERS:
|
||||
size = sizeof(val.flag);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
|
||||
if (size) {
|
||||
if (optlen != size)
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_sockptr(&val, optval, size))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
err = raw_setsockopt_locked(sock, optname, optval, optlen);
|
||||
err = raw_setsockopt_locked(sock, optname, &val, filter, count);
|
||||
|
||||
release_sock(sk);
|
||||
rtnl_unlock();
|
||||
|
||||
/* on error free the filters allocated for CAN_RAW_FILTER above */
|
||||
if (err && count > 1)
|
||||
kfree(filter);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
@ -784,6 +828,7 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
|
|||
|
||||
switch (optname) {
|
||||
case CAN_RAW_FILTER: {
|
||||
struct can_filter *filter = NULL;
|
||||
int err = 0;
|
||||
|
||||
lock_sock(sk);
|
||||
|
|
@ -798,15 +843,22 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
|
|||
} else {
|
||||
if (len > fsize)
|
||||
len = fsize;
|
||||
if (copy_to_iter(ro->filter, len,
|
||||
&opt->iter_out) != len)
|
||||
err = -EFAULT;
|
||||
/* copy to user space after release_sock() */
|
||||
filter = kmemdup(ro->filter, len, GFP_KERNEL);
|
||||
if (!filter)
|
||||
err = -ENOMEM;
|
||||
}
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
release_sock(sk);
|
||||
|
||||
if (filter) {
|
||||
if (copy_to_iter(filter, len, &opt->iter_out) != len)
|
||||
err = -EFAULT;
|
||||
kfree(filter);
|
||||
}
|
||||
|
||||
if (!err)
|
||||
opt->optlen = len;
|
||||
return err;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user