aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorEric Dumazet2013-03-28 22:01:22 -0500
committerGreg Kroah-Hartman2013-04-05 11:26:19 -0500
commit09269638aab0f527678426fe9258b384fe6bb103 (patch)
tree1edaa8cdd216ba0692e8f2203b2f8675848f4cd9 /net
parent301ccb66b7a86ef1827f9910bc8ad4a7722ab2e9 (diff)
downloadkernel-omap-09269638aab0f527678426fe9258b384fe6bb103.tar.gz
kernel-omap-09269638aab0f527678426fe9258b384fe6bb103.tar.xz
kernel-omap-09269638aab0f527678426fe9258b384fe6bb103.zip
net: add a synchronize_net() in netdev_rx_handler_unregister()
[ Upstream commit 00cfec37484761a44a3b6f4675a54caa618210ae ] commit 35d48903e97819 (bonding: fix rx_handler locking) added a race in bonding driver, reported by Steven Rostedt who did a very good diagnosis : <quoting Steven> I'm currently debugging a crash in an old 3.0-rt kernel that one of our customers is seeing. The bug happens with a stress test that loads and unloads the bonding module in a loop (I don't know all the details as I'm not the one that is directly interacting with the customer). But the bug looks to be something that may still be present and possibly present in mainline too. It will just be much harder to trigger it in mainline. In -rt, interrupts are threads, and can schedule in and out just like any other thread. Note, mainline now supports interrupt threads so this may be easily reproducible in mainline as well. I don't have the ability to tell the customer to try mainline or other kernels, so my hands are somewhat tied to what I can do. But according to a core dump, I tracked down that the eth irq thread crashed in bond_handle_frame() here: slave = bond_slave_get_rcu(skb->dev); bond = slave->bond; <--- BUG the slave returned was NULL and accessing slave->bond caused a NULL pointer dereference. Looking at the code that unregisters the handler: void netdev_rx_handler_unregister(struct net_device *dev) { ASSERT_RTNL(); RCU_INIT_POINTER(dev->rx_handler, NULL); RCU_INIT_POINTER(dev->rx_handler_data, NULL); } Which is basically: dev->rx_handler = NULL; dev->rx_handler_data = NULL; And looking at __netif_receive_skb() we have: rx_handler = rcu_dereference(skb->dev->rx_handler); if (rx_handler) { if (pt_prev) { ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = NULL; } switch (rx_handler(&skb)) { My question to all of you is, what stops this interrupt from happening while the bonding module is unloading? What happens if the interrupt triggers and we have this: CPU0 CPU1 ---- ---- rx_handler = skb->dev->rx_handler netdev_rx_handler_unregister() { dev->rx_handler = NULL; dev->rx_handler_data = NULL; rx_handler() bond_handle_frame() { slave = skb->dev->rx_handler; bond = slave->bond; <-- NULL pointer dereference!!! What protection am I missing in the bond release handler that would prevent the above from happening? </quoting Steven> We can fix bug this in two ways. First is adding a test in bond_handle_frame() and others to check if rx_handler_data is NULL. A second way is adding a synchronize_net() in netdev_rx_handler_unregister() to make sure that a rcu protected reader has the guarantee to see a non NULL rx_handler_data. The second way is better as it avoids an extra test in fast path. Reported-by: Steven Rostedt <rostedt@goodmis.org> Signed-off-by: Eric Dumazet <edumazet@google.com> Cc: Jiri Pirko <jpirko@redhat.com> Cc: Paul E. McKenney <paulmck@us.ibm.com> Acked-by: Steven Rostedt <rostedt@goodmis.org> Reviewed-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com> Signed-off-by: David S. Miller <davem@davemloft.net> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'net')
-rw-r--r--net/core/dev.c6
1 files changed, 6 insertions, 0 deletions
diff --git a/net/core/dev.c b/net/core/dev.c
index 46c2bbb2bfb5..5d9c43dca730 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3276,6 +3276,7 @@ int netdev_rx_handler_register(struct net_device *dev,
3276 if (dev->rx_handler) 3276 if (dev->rx_handler)
3277 return -EBUSY; 3277 return -EBUSY;
3278 3278
3279 /* Note: rx_handler_data must be set before rx_handler */
3279 rcu_assign_pointer(dev->rx_handler_data, rx_handler_data); 3280 rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);
3280 rcu_assign_pointer(dev->rx_handler, rx_handler); 3281 rcu_assign_pointer(dev->rx_handler, rx_handler);
3281 3282
@@ -3296,6 +3297,11 @@ void netdev_rx_handler_unregister(struct net_device *dev)
3296 3297
3297 ASSERT_RTNL(); 3298 ASSERT_RTNL();
3298 RCU_INIT_POINTER(dev->rx_handler, NULL); 3299 RCU_INIT_POINTER(dev->rx_handler, NULL);
3300 /* a reader seeing a non NULL rx_handler in a rcu_read_lock()
3301 * section has a guarantee to see a non NULL rx_handler_data
3302 * as well.
3303 */
3304 synchronize_net();
3299 RCU_INIT_POINTER(dev->rx_handler_data, NULL); 3305 RCU_INIT_POINTER(dev->rx_handler_data, NULL);
3300} 3306}
3301EXPORT_SYMBOL_GPL(netdev_rx_handler_unregister); 3307EXPORT_SYMBOL_GPL(netdev_rx_handler_unregister);