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

net sched: fix race in mirred device removal

This fixes hang when target device of mirred packet classifier
action is removed.

If a mirror or redirection action is configured to cause packets
to go to another device, the classifier holds a ref count, but was assuming
the adminstrator cleaned up all redirections before removing. The fix
is to add a notifier and cleanup during unregister.

The new list is implicitly protected by RTNL mutex because
it is held during filter add/delete as well as notifier.

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
Acked-by: Jamal Hadi Salim <hadi@cyberus.ca>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

stephen hemminger and committed by
David S. Miller
3b87956e 76ac21f5

+41 -3
+1
include/net/tc_act/tc_mirred.h
··· 9 9 int tcfm_ifindex; 10 10 int tcfm_ok_push; 11 11 struct net_device *tcfm_dev; 12 + struct list_head tcfm_list; 12 13 }; 13 14 #define to_mirred(pc) \ 14 15 container_of(pc, struct tcf_mirred, common)
+40 -3
net/sched/act_mirred.c
··· 33 33 static struct tcf_common *tcf_mirred_ht[MIRRED_TAB_MASK + 1]; 34 34 static u32 mirred_idx_gen; 35 35 static DEFINE_RWLOCK(mirred_lock); 36 + static LIST_HEAD(mirred_list); 36 37 37 38 static struct tcf_hashinfo mirred_hash_info = { 38 39 .htab = tcf_mirred_ht, ··· 48 47 m->tcf_bindcnt--; 49 48 m->tcf_refcnt--; 50 49 if(!m->tcf_bindcnt && m->tcf_refcnt <= 0) { 51 - dev_put(m->tcfm_dev); 50 + list_del(&m->tcfm_list); 51 + if (m->tcfm_dev) 52 + dev_put(m->tcfm_dev); 52 53 tcf_hash_destroy(&m->common, &mirred_hash_info); 53 54 return 1; 54 55 } ··· 137 134 m->tcfm_ok_push = ok_push; 138 135 } 139 136 spin_unlock_bh(&m->tcf_lock); 140 - if (ret == ACT_P_CREATED) 137 + if (ret == ACT_P_CREATED) { 138 + list_add(&m->tcfm_list, &mirred_list); 141 139 tcf_hash_insert(pc, &mirred_hash_info); 140 + } 142 141 143 142 return ret; 144 143 } ··· 167 162 m->tcf_tm.lastuse = jiffies; 168 163 169 164 dev = m->tcfm_dev; 165 + if (!dev) { 166 + printk_once(KERN_NOTICE "tc mirred: target device is gone\n"); 167 + goto out; 168 + } 169 + 170 170 if (!(dev->flags & IFF_UP)) { 171 171 if (net_ratelimit()) 172 - pr_notice("tc mirred to Houston: device %s is gone!\n", 172 + pr_notice("tc mirred to Houston: device %s is down\n", 173 173 dev->name); 174 174 goto out; 175 175 } ··· 242 232 return -1; 243 233 } 244 234 235 + static int mirred_device_event(struct notifier_block *unused, 236 + unsigned long event, void *ptr) 237 + { 238 + struct net_device *dev = ptr; 239 + struct tcf_mirred *m; 240 + 241 + if (event == NETDEV_UNREGISTER) 242 + list_for_each_entry(m, &mirred_list, tcfm_list) { 243 + if (m->tcfm_dev == dev) { 244 + dev_put(dev); 245 + m->tcfm_dev = NULL; 246 + } 247 + } 248 + 249 + return NOTIFY_DONE; 250 + } 251 + 252 + static struct notifier_block mirred_device_notifier = { 253 + .notifier_call = mirred_device_event, 254 + }; 255 + 256 + 245 257 static struct tc_action_ops act_mirred_ops = { 246 258 .kind = "mirred", 247 259 .hinfo = &mirred_hash_info, ··· 284 252 285 253 static int __init mirred_init_module(void) 286 254 { 255 + int err = register_netdevice_notifier(&mirred_device_notifier); 256 + if (err) 257 + return err; 258 + 287 259 pr_info("Mirror/redirect action on\n"); 288 260 return tcf_register_action(&act_mirred_ops); 289 261 } 290 262 291 263 static void __exit mirred_cleanup_module(void) 292 264 { 265 + unregister_netdevice_notifier(&mirred_device_notifier); 293 266 tcf_unregister_action(&act_mirred_ops); 294 267 } 295 268