Curt Brune
2013-Dec-28 22:37 UTC
[PATCH 1/1] bridge/br_multicast: use spin_lock_bh() in br_multicast_set_hash_max()
br_multicast_set_hash_max() is called from process context in net/bridge/br_sysfs_br.c by the sysfs store_hash_max() function. br_multicast_set_hash_max() calls spin_lock(&br->multicast_lock), which can deadlock the CPU if a softirq that also tries to take the same lock interrupts br_multicast_set_hash_max() while the lock is held . This can happen quite easily when any of the bridge multicast timers expire, which try to take the same lock. The fix here is to use spin_lock_bh(), preventing other softirqs from executing on this CPU. Steps to reproduce: 1. Create a bridge with several interfaces (I used 4). 2. Set the "multicast query interval" to a low number, like 1. 3. Enable the bridge as a multicast querier. 4. Repeatedly set the bridge hash_max parameter via sysfs. # brctl addbr br0 # brctl addif br0 eth1 eth2 eth3 eth4 # brctl setmcqi br0 1 # brctl setmcquerier br0 1 # while true ; do > echo 4096 > /sys/class/net/br0/bridge/hash_max > done Signed-off-by: Curt Brune <curt@cumulusnetworks.com> --- net/bridge/br_multicast.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 4c214b2..ef66365 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -1901,136 +1901,136 @@ static void br_multicast_start_querier(struct net_bridge *br, struct net_bridge_port *port; __br_multicast_open(br, query); list_for_each_entry(port, &br->port_list, list) { if (port->state == BR_STATE_DISABLED || port->state == BR_STATE_BLOCKING) continue; if (query == &br->ip4_query) br_multicast_enable(&port->ip4_query); #if IS_ENABLED(CONFIG_IPV6) else br_multicast_enable(&port->ip6_query); #endif } } int br_multicast_toggle(struct net_bridge *br, unsigned long val) { int err = 0; struct net_bridge_mdb_htable *mdb; spin_lock_bh(&br->multicast_lock); if (br->multicast_disabled == !val) goto unlock; br->multicast_disabled = !val; if (br->multicast_disabled) goto unlock; if (!netif_running(br->dev)) goto unlock; mdb = mlock_dereference(br->mdb, br); if (mdb) { if (mdb->old) { err = -EEXIST; rollback: br->multicast_disabled = !!val; goto unlock; } err = br_mdb_rehash(&br->mdb, mdb->max, br->hash_elasticity); if (err) goto rollback; } br_multicast_start_querier(br, &br->ip4_query); #if IS_ENABLED(CONFIG_IPV6) br_multicast_start_querier(br, &br->ip6_query); #endif unlock: spin_unlock_bh(&br->multicast_lock); return err; } int br_multicast_set_querier(struct net_bridge *br, unsigned long val) { unsigned long max_delay; val = !!val; spin_lock_bh(&br->multicast_lock); if (br->multicast_querier == val) goto unlock; br->multicast_querier = val; if (!val) goto unlock; max_delay = br->multicast_query_response_interval; if (!timer_pending(&br->ip4_querier.timer)) br->ip4_querier.delay_time = jiffies + max_delay; br_multicast_start_querier(br, &br->ip4_query); #if IS_ENABLED(CONFIG_IPV6) if (!timer_pending(&br->ip6_querier.timer)) br->ip6_querier.delay_time = jiffies + max_delay; br_multicast_start_querier(br, &br->ip6_query); #endif unlock: spin_unlock_bh(&br->multicast_lock); return 0; } int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val) { int err = -ENOENT; u32 old; struct net_bridge_mdb_htable *mdb; - spin_lock(&br->multicast_lock); + spin_lock_bh(&br->multicast_lock); if (!netif_running(br->dev)) goto unlock; err = -EINVAL; if (!is_power_of_2(val)) goto unlock; mdb = mlock_dereference(br->mdb, br); if (mdb && val < mdb->size) goto unlock; err = 0; old = br->hash_max; br->hash_max = val; if (mdb) { if (mdb->old) { err = -EEXIST; rollback: br->hash_max = old; goto unlock; } err = br_mdb_rehash(&br->mdb, br->hash_max, br->hash_elasticity); if (err) goto rollback; } unlock: - spin_unlock(&br->multicast_lock); + spin_unlock_bh(&br->multicast_lock); return err; } -- 1.7.10.4