can: raw: add missing refcount for memory leak fix
authorOliver Hartkopp <socketcan@hartkopp.net>
Mon, 21 Aug 2023 14:45:47 +0000 (16:45 +0200)
committerJakub Kicinski <kuba@kernel.org>
Wed, 23 Aug 2023 00:18:50 +0000 (17:18 -0700)
Commit ee8b94c8510c ("can: raw: fix receiver memory leak") introduced
a new reference to the CAN netdevice that has assigned CAN filters.
But this new ro->dev reference did not maintain its own refcount which
lead to another KASAN use-after-free splat found by Eric Dumazet.

This patch ensures a proper refcount for the CAN nedevice.

Fixes: ee8b94c8510c ("can: raw: fix receiver memory leak")
Reported-by: Eric Dumazet <edumazet@google.com>
Cc: Ziyang Xuan <william.xuanziyang@huawei.com>
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://lore.kernel.org/r/20230821144547.6658-3-socketcan@hartkopp.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/can/raw.c

index e10f593..d50c3f3 100644 (file)
@@ -85,6 +85,7 @@ struct raw_sock {
        int bound;
        int ifindex;
        struct net_device *dev;
+       netdevice_tracker dev_tracker;
        struct list_head notifier;
        int loopback;
        int recv_own_msgs;
@@ -285,8 +286,10 @@ static void raw_notify(struct raw_sock *ro, unsigned long msg,
        case NETDEV_UNREGISTER:
                lock_sock(sk);
                /* remove current filters & unregister */
-               if (ro->bound)
+               if (ro->bound) {
                        raw_disable_allfilters(dev_net(dev), dev, sk);
+                       netdev_put(dev, &ro->dev_tracker);
+               }
 
                if (ro->count > 1)
                        kfree(ro->filter);
@@ -391,10 +394,12 @@ static int raw_release(struct socket *sock)
 
        /* remove current filters & unregister */
        if (ro->bound) {
-               if (ro->dev)
+               if (ro->dev) {
                        raw_disable_allfilters(dev_net(ro->dev), ro->dev, sk);
-               else
+                       netdev_put(ro->dev, &ro->dev_tracker);
+               } else {
                        raw_disable_allfilters(sock_net(sk), NULL, sk);
+               }
        }
 
        if (ro->count > 1)
@@ -445,10 +450,10 @@ static int raw_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->flags & IFF_UP))
                        notify_enetdown = 1;
 
@@ -456,7 +461,9 @@ static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len)
 
                /* filters set by default/setsockopt */
                err = raw_enable_allfilters(sock_net(sk), dev, sk);
-               dev_put(dev);
+               if (err)
+                       goto out_put_dev;
+
        } else {
                ifindex = 0;
 
@@ -467,18 +474,28 @@ static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len)
        if (!err) {
                if (ro->bound) {
                        /* unregister old filters */
-                       if (ro->dev)
+                       if (ro->dev) {
                                raw_disable_allfilters(dev_net(ro->dev),
                                                       ro->dev, sk);
-                       else
+                               /* drop reference to old ro->dev */
+                               netdev_put(ro->dev, &ro->dev_tracker);
+                       } else {
                                raw_disable_allfilters(sock_net(sk), NULL, sk);
+                       }
                }
                ro->ifindex = ifindex;
                ro->bound = 1;
+               /* bind() ok -> hold a reference for new ro->dev */
                ro->dev = dev;
+               if (ro->dev)
+                       netdev_hold(ro->dev, &ro->dev_tracker, GFP_KERNEL);
        }
 
- out:
+out_put_dev:
+       /* remove potential reference from dev_get_by_index() */
+       if (dev)
+               dev_put(dev);
+out:
        release_sock(sk);
        rtnl_unlock();