can: af_can: fix NULL pointer dereference in can_rx_register()
[platform/kernel/linux-rpi.git] / net / can / bcm.c
index f3e4d95..aab3a18 100644 (file)
@@ -100,6 +100,7 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)
 
 struct bcm_op {
        struct list_head list;
+       struct rcu_head rcu;
        int ifindex;
        canid_t can_id;
        u32 flags;
@@ -273,6 +274,7 @@ static void bcm_can_tx(struct bcm_op *op)
        struct sk_buff *skb;
        struct net_device *dev;
        struct canfd_frame *cf = op->frames + op->cfsiz * op->currframe;
+       int err;
 
        /* no target device? => exit */
        if (!op->ifindex)
@@ -297,11 +299,11 @@ static void bcm_can_tx(struct bcm_op *op)
        /* send with loopback */
        skb->dev = dev;
        can_skb_set_owner(skb, op->sk);
-       can_send(skb, 1);
+       err = can_send(skb, 1);
+       if (!err)
+               op->frames_abs++;
 
-       /* update statistics */
        op->currframe++;
-       op->frames_abs++;
 
        /* reached last frame? */
        if (op->currframe >= op->nframes)
@@ -718,10 +720,9 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,
        return NULL;
 }
 
-static void bcm_remove_op(struct bcm_op *op)
+static void bcm_free_op_rcu(struct rcu_head *rcu_head)
 {
-       hrtimer_cancel(&op->timer);
-       hrtimer_cancel(&op->thrtimer);
+       struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu);
 
        if ((op->frames) && (op->frames != &op->sframe))
                kfree(op->frames);
@@ -732,6 +733,14 @@ static void bcm_remove_op(struct bcm_op *op)
        kfree(op);
 }
 
+static void bcm_remove_op(struct bcm_op *op)
+{
+       hrtimer_cancel(&op->timer);
+       hrtimer_cancel(&op->thrtimer);
+
+       call_rcu(&op->rcu, bcm_free_op_rcu);
+}
+
 static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
 {
        if (op->rx_reg_dev == dev) {
@@ -757,6 +766,9 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
                if ((op->can_id == mh->can_id) && (op->ifindex == ifindex) &&
                    (op->flags & CAN_FD_FRAME) == (mh->flags & CAN_FD_FRAME)) {
 
+                       /* 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
@@ -1417,7 +1429,7 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
                if (notify_enodev) {
                        sk->sk_err = ENODEV;
                        if (!sock_flag(sk, SOCK_DEAD))
-                               sk->sk_error_report(sk);
+                               sk_error_report(sk);
                }
                break;
 
@@ -1425,7 +1437,7 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
                if (bo->bound && bo->ifindex == dev->ifindex) {
                        sk->sk_err = ENETDOWN;
                        if (!sock_flag(sk, SOCK_DEAD))
-                               sk->sk_error_report(sk);
+                               sk_error_report(sk);
                }
        }
 }
@@ -1533,9 +1545,13 @@ static int bcm_release(struct socket *sock)
                                          REGMASK(op->can_id),
                                          bcm_rx_handler, op);
 
-               bcm_remove_op(op);
        }
 
+       synchronize_rcu();
+
+       list_for_each_entry_safe(op, next, &bo->rx_ops, list)
+               bcm_remove_op(op);
+
 #if IS_ENABLED(CONFIG_PROC_FS)
        /* remove procfs entry */
        if (net->can.bcmproc_dir && bo->bcm_proc_read)