diff mbox series

[net,v4,12/12] virt_wifi: fix refcnt leak in module exit routine

Message ID 20190928164843.31800-13-ap420073@gmail.com (mailing list archive)
State Not Applicable
Delegated to: Kalle Valo
Headers show
Series net: fix nested device bugs | expand

Commit Message

Taehee Yoo Sept. 28, 2019, 4:48 p.m. UTC
virt_wifi_newlink() calls netdev_upper_dev_link() and it internally
holds reference count of lower interface.

Current code does not release a reference count of the lower interface
when the lower interface is being deleted.
So, reference count leaks occur.

Test commands:
    ip link add dummy0 type dummy
    ip link add vw1 link dummy0 type virt_wifi

Splat looks like:
[  182.001918][ T1333] WARNING: CPU: 0 PID: 1333 at net/core/dev.c:8638 rollback_registered_many+0x75d/0xda0
[  182.002724][ T1333] Modules linked in: virt_wifi cfg80211 dummy veth openvswitch nsh nf_conncount nf_nat nf_conntrack6
[  182.002724][ T1333] CPU: 0 PID: 1333 Comm: ip Not tainted 5.3.0+ #370
[  182.002724][ T1333] RIP: 0010:rollback_registered_many+0x75d/0xda0
[  182.002724][ T1333] Code: 0c 00 00 48 89 de 4c 89 ff e8 6f 5a 04 00 48 89 df e8 c7 26 fd ff 84 c0 0f 84 a5 fd ff ff 40
[  182.002724][ T1333] RSP: 0018:ffff88810900f348 EFLAGS: 00010286
[  182.002724][ T1333] RAX: 0000000000000024 RBX: ffff88811361d700 RCX: 0000000000000000
[  182.002724][ T1333] RDX: 0000000000000024 RSI: 0000000000000008 RDI: ffffed1021201e5f
[  182.002724][ T1333] RBP: ffff88810900f4e0 R08: ffffed1022c3ff71 R09: ffffed1022c3ff71
[  182.002724][ T1333] R10: 0000000000000001 R11: ffffed1022c3ff70 R12: dffffc0000000000
[  182.002724][ T1333] R13: ffff88810900f460 R14: ffff88810900f420 R15: ffff8881075f1940
[  182.002724][ T1333] FS:  00007f77c42240c0(0000) GS:ffff888116000000(0000) knlGS:0000000000000000
[  182.002724][ T1333] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  182.002724][ T1333] CR2: 00007f77c3706240 CR3: 000000011139e000 CR4: 00000000001006f0
[  182.002724][ T1333] Call Trace:
[  182.002724][ T1333]  ? generic_xdp_install+0x310/0x310
[  182.002724][ T1333]  ? check_chain_key+0x236/0x5d0
[  182.002724][ T1333]  ? __nla_validate_parse+0x98/0x1ad0
[  182.002724][ T1333]  unregister_netdevice_many.part.123+0x13/0x1b0
[  182.002724][ T1333]  rtnl_delete_link+0xbc/0x100
[  182.002724][ T1333]  ? rtnl_af_register+0xc0/0xc0
[  182.002724][ T1333]  rtnl_dellink+0x2e7/0x870
[ ... ]

[  192.874736][ T1333] unregister_netdevice: waiting for dummy0 to become free. Usage count = 1

This patch adds notifier routine to delete upper interface before deleting
lower interface.

Fixes: c7cdba31ed8b ("mac80211-next: rtnetlink wifi simulation device")
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
---

v4:
 - Add this new patch to fix refcnt leaks in the virt_wifi module

 drivers/net/wireless/virt_wifi.c | 51 ++++++++++++++++++++++++++++++--
 1 file changed, 49 insertions(+), 2 deletions(-)

Comments

Johannes Berg Sept. 28, 2019, 6:57 p.m. UTC | #1
On Sat, 2019-09-28 at 16:48 +0000, Taehee Yoo wrote:
> virt_wifi_newlink() calls netdev_upper_dev_link() and it internally
> holds reference count of lower interface.
[...]
> This patch adds notifier routine to delete upper interface before deleting
> lower interface.

Good catch, thanks!

For now I'll assume this will go in through net together with the whole
series (once ready), shout if you want something else.

johannes
Sabrina Dubroca Oct. 7, 2019, 11:22 a.m. UTC | #2
2019-09-28, 16:48:43 +0000, Taehee Yoo wrote:
> virt_wifi_newlink() calls netdev_upper_dev_link() and it internally
> holds reference count of lower interface.
> 
> Current code does not release a reference count of the lower interface
> when the lower interface is being deleted.
> So, reference count leaks occur.
> 
> Test commands:
>     ip link add dummy0 type dummy
>     ip link add vw1 link dummy0 type virt_wifi

There should also be "ip link del dummy0" in this reproducer, right?

[...]

> @@ -598,14 +634,24 @@ static int __init virt_wifi_init_module(void)
>  	/* Guaranteed to be locallly-administered and not multicast. */
>  	eth_random_addr(fake_router_bssid);
>  
> +	err = register_netdevice_notifier(&virt_wifi_notifier);
> +	if (err)
> +		return err;
> +

Here err is 0.

>  	common_wiphy = virt_wifi_make_wiphy();
>  	if (!common_wiphy)
> -		return -ENOMEM;
> +		goto notifier;

err is still 0 when we jump...

>  	err = rtnl_link_register(&virt_wifi_link_ops);
>  	if (err)
> -		virt_wifi_destroy_wiphy(common_wiphy);
> +		goto destroy_wiphy;
>  
> +	return 0;
> +
> +destroy_wiphy:
> +	virt_wifi_destroy_wiphy(common_wiphy);
> +notifier:
> +	unregister_netdevice_notifier(&virt_wifi_notifier);
>  	return err;
>  }

... so now we return 0 on failure. Can you add an "err = -ENOMEM"
before "common_wiphy = ..."?

Thanks.
Taehee Yoo Oct. 8, 2019, 6:53 a.m. UTC | #3
On Mon, 7 Oct 2019 at 20:22, Sabrina Dubroca <sd@queasysnail.net> wrote:
>

Hi Sabrina,
Thank you for the review!

> 2019-09-28, 16:48:43 +0000, Taehee Yoo wrote:
> > virt_wifi_newlink() calls netdev_upper_dev_link() and it internally
> > holds reference count of lower interface.
> >
> > Current code does not release a reference count of the lower interface
> > when the lower interface is being deleted.
> > So, reference count leaks occur.
> >
> > Test commands:
> >     ip link add dummy0 type dummy
> >     ip link add vw1 link dummy0 type virt_wifi
>
> There should also be "ip link del dummy0" in this reproducer, right?
>
> [...]
>
> > @@ -598,14 +634,24 @@ static int __init virt_wifi_init_module(void)
> >       /* Guaranteed to be locallly-administered and not multicast. */
> >       eth_random_addr(fake_router_bssid);
> >
> > +     err = register_netdevice_notifier(&virt_wifi_notifier);
> > +     if (err)
> > +             return err;
> > +
>
> Here err is 0.
>
> >       common_wiphy = virt_wifi_make_wiphy();
> >       if (!common_wiphy)
> > -             return -ENOMEM;
> > +             goto notifier;
>
> err is still 0 when we jump...
>
> >       err = rtnl_link_register(&virt_wifi_link_ops);
> >       if (err)
> > -             virt_wifi_destroy_wiphy(common_wiphy);
> > +             goto destroy_wiphy;
> >
> > +     return 0;
> > +
> > +destroy_wiphy:
> > +     virt_wifi_destroy_wiphy(common_wiphy);
> > +notifier:
> > +     unregister_netdevice_notifier(&virt_wifi_notifier);
> >       return err;
> >  }
>
> ... so now we return 0 on failure. Can you add an "err = -ENOMEM"
> before "common_wiphy = ..."?
>

You're right, I will fix this in a v5 patch!

Thanks!

> Thanks.
>
> --
> Sabrina
diff mbox series

Patch

diff --git a/drivers/net/wireless/virt_wifi.c b/drivers/net/wireless/virt_wifi.c
index be92e1220284..aadbacb01c8d 100644
--- a/drivers/net/wireless/virt_wifi.c
+++ b/drivers/net/wireless/virt_wifi.c
@@ -590,6 +590,42 @@  static struct rtnl_link_ops virt_wifi_link_ops = {
 	.priv_size	= sizeof(struct virt_wifi_netdev_priv),
 };
 
+static inline bool netif_is_virt_wifi_dev(const struct net_device *dev)
+{
+	return rcu_access_pointer(dev->rx_handler) == virt_wifi_rx_handler;
+}
+
+static int virt_wifi_event(struct notifier_block *this, unsigned long event,
+			   void *ptr)
+{
+	struct net_device *lower_dev = netdev_notifier_info_to_dev(ptr);
+	struct virt_wifi_netdev_priv *priv;
+	struct net_device *upper_dev;
+	LIST_HEAD(list_kill);
+
+	if (!netif_is_virt_wifi_dev(lower_dev))
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case NETDEV_UNREGISTER:
+		priv = rtnl_dereference(lower_dev->rx_handler_data);
+		if (!priv)
+			return NOTIFY_DONE;
+
+		upper_dev = priv->upperdev;
+
+		upper_dev->rtnl_link_ops->dellink(upper_dev, &list_kill);
+		unregister_netdevice_many(&list_kill);
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block virt_wifi_notifier = {
+	.notifier_call = virt_wifi_event,
+};
+
 /* Acquires and releases the rtnl lock. */
 static int __init virt_wifi_init_module(void)
 {
@@ -598,14 +634,24 @@  static int __init virt_wifi_init_module(void)
 	/* Guaranteed to be locallly-administered and not multicast. */
 	eth_random_addr(fake_router_bssid);
 
+	err = register_netdevice_notifier(&virt_wifi_notifier);
+	if (err)
+		return err;
+
 	common_wiphy = virt_wifi_make_wiphy();
 	if (!common_wiphy)
-		return -ENOMEM;
+		goto notifier;
 
 	err = rtnl_link_register(&virt_wifi_link_ops);
 	if (err)
-		virt_wifi_destroy_wiphy(common_wiphy);
+		goto destroy_wiphy;
 
+	return 0;
+
+destroy_wiphy:
+	virt_wifi_destroy_wiphy(common_wiphy);
+notifier:
+	unregister_netdevice_notifier(&virt_wifi_notifier);
 	return err;
 }
 
@@ -615,6 +661,7 @@  static void __exit virt_wifi_cleanup_module(void)
 	/* Will delete any devices that depend on the wiphy. */
 	rtnl_link_unregister(&virt_wifi_link_ops);
 	virt_wifi_destroy_wiphy(common_wiphy);
+	unregister_netdevice_notifier(&virt_wifi_notifier);
 }
 
 module_init(virt_wifi_init_module);