[PATCH] bridge: fix RCU race on device removal
authorStephen Hemminger <shemminger@osdl.org>
Mon, 6 Feb 2006 23:42:45 +0000 (15:42 -0800)
committerChris Wright <chrisw@sous-sol.org>
Fri, 10 Feb 2006 07:20:15 +0000 (23:20 -0800)
Patch to 2.6.15 stable kernel to fix race conditions on device
removal.  These are reproducible by doing delif while packets are
in flight.

Signed-off-by: Stephen Hemminger <shemminger@osdl.org>
Signed-off-by: Chris Wright <chrisw@sous-sol.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
net/bridge/br_if.c
net/bridge/br_input.c
net/bridge/br_stp_bpdu.c

index 975abe254b7a9b37462974fdc7448f9cd22cac8d..c085d7586e99a9872e2bda9de9b6375ce955fe71 100644 (file)
@@ -99,7 +99,6 @@ static void del_nbp(struct net_bridge_port *p)
        struct net_bridge *br = p->br;
        struct net_device *dev = p->dev;
 
-       dev->br_port = NULL;
        dev_set_promiscuity(dev, -1);
 
        spin_lock_bh(&br->lock);
@@ -110,9 +109,7 @@ static void del_nbp(struct net_bridge_port *p)
 
        list_del_rcu(&p->list);
 
-       del_timer_sync(&p->message_age_timer);
-       del_timer_sync(&p->forward_delay_timer);
-       del_timer_sync(&p->hold_timer);
+       rcu_assign_pointer(dev->br_port, NULL);
        
        call_rcu(&p->rcu, destroy_nbp_rcu);
 }
@@ -217,7 +214,6 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br,
        p->dev = dev;
        p->path_cost = cost;
        p->priority = 0x8000 >> BR_PORT_BITS;
-       dev->br_port = p;
        p->port_no = index;
        br_init_port(p);
        p->state = BR_STATE_DISABLED;
@@ -360,6 +356,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
        else if ((err = br_sysfs_addif(p)))
                del_nbp(p);
        else {
+               rcu_assign_pointer(dev->br_port, p);
                dev_set_promiscuity(dev, 1);
 
                list_add_rcu(&p->list, &br->port_list);
index b88220a64cd8e0a7009971cc09f6507fc3f9b117..c027ac3b904321ac0598c03b7156f2551c98c63d 100644 (file)
@@ -45,11 +45,17 @@ static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
 int br_handle_frame_finish(struct sk_buff *skb)
 {
        const unsigned char *dest = eth_hdr(skb)->h_dest;
-       struct net_bridge_port *p = skb->dev->br_port;
-       struct net_bridge *br = p->br;
+       struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
+       struct net_bridge *br;
        struct net_bridge_fdb_entry *dst;
        int passedup = 0;
 
+       if (unlikely(!p || p->state == BR_STATE_DISABLED)) {
+               kfree_skb(skb);
+               return 0;
+       }
+
+       br = p->br;
        /* insert into forwarding database after filtering to avoid spoofing */
        br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
 
index d071f1c9ad0b00076c18ce677ed1ccba0bc300ec..78b8f28063594d22839b91bbd9586d1e3b567c57 100644 (file)
@@ -136,10 +136,13 @@ static const unsigned char header[6] = {0x42, 0x42, 0x03, 0x00, 0x00, 0x00};
 /* NO locks */
 int br_stp_handle_bpdu(struct sk_buff *skb)
 {
-       struct net_bridge_port *p = skb->dev->br_port;
-       struct net_bridge *br = p->br;
+       struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
+       struct net_bridge *br;
        unsigned char *buf;
 
+       if (!p)
+               goto err;
+
        /* insert into forwarding database after filtering to avoid spoofing */
        br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
 
@@ -150,6 +153,7 @@ int br_stp_handle_bpdu(struct sk_buff *skb)
 
        buf = skb_pull(skb, sizeof(header));
 
+       br = p->br;
        spin_lock_bh(&br->lock);
        if (p->state == BR_STATE_DISABLED 
            || !(br->dev->flags & IFF_UP)