Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

macsec: fix refcnt leak in module exit routine

When a macsec interface is created, it increases a refcnt to a lower
device(real device). when macsec interface is deleted, the refcnt is
decreased in macsec_free_netdev(), which is ->priv_destructor() of
macsec interface.

The problem scenario is this.
When nested macsec interfaces are exiting, the exit routine of the
macsec module makes refcnt leaks.

Test commands:
ip link add dummy0 type dummy
ip link add macsec0 link dummy0 type macsec
ip link add macsec1 link macsec0 type macsec
modprobe -rv macsec

[ 208.629433] unregister_netdevice: waiting for macsec0 to become free. Usage count = 1

Steps of exit routine of macsec module are below.
1. Calls ->dellink() in __rtnl_link_unregister().
2. Checks refcnt and wait refcnt to be 0 if refcnt is not 0 in
netdev_run_todo().
3. Calls ->priv_destruvtor() in netdev_run_todo().

Step2 checks refcnt, but step3 decreases refcnt.
So, step2 waits forever.

This patch makes the macsec module do not hold a refcnt of the lower
device because it already holds a refcnt of the lower device with
netdev_upper_dev_link().

Fixes: c09440f7dcb3 ("macsec: introduce IEEE 802.1AE driver")
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Taehee Yoo and committed by
David S. Miller
2bce1ebe 369f61be

-4
-4
drivers/net/macsec.c
··· 3000 3000 static void macsec_free_netdev(struct net_device *dev) 3001 3001 { 3002 3002 struct macsec_dev *macsec = macsec_priv(dev); 3003 - struct net_device *real_dev = macsec->real_dev; 3004 3003 3005 3004 free_percpu(macsec->stats); 3006 3005 free_percpu(macsec->secy.tx_sc.stats); 3007 3006 3008 - dev_put(real_dev); 3009 3007 } 3010 3008 3011 3009 static void macsec_setup(struct net_device *dev) ··· 3257 3259 err = register_netdevice(dev); 3258 3260 if (err < 0) 3259 3261 return err; 3260 - 3261 - dev_hold(real_dev); 3262 3262 3263 3263 macsec->nest_level = dev_get_nest_level(real_dev) + 1; 3264 3264