diff options
author | stephen hemminger <shemminger@vyatta.com> | 2010-11-15 01:38:13 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-11-15 14:13:17 -0500 |
commit | b5ed54e94d324f17c97852296d61a143f01b227a (patch) | |
tree | 2104d3cde24efc1145bc71beb4b61d79ed841132 /net/bridge | |
parent | 61391cde9eefac5cfcf6d214aa80c77e58b1626b (diff) |
bridge: fix RCU races with bridge port
The macro br_port_exists() is not enough protection when only
RCU is being used. There is a tiny race where other CPU has cleared port
handler hook, but is bridge port flag might still be set.
Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/bridge')
-rw-r--r-- | net/bridge/br_fdb.c | 15 | ||||
-rw-r--r-- | net/bridge/br_if.c | 5 | ||||
-rw-r--r-- | net/bridge/br_netfilter.c | 13 | ||||
-rw-r--r-- | net/bridge/br_netlink.c | 10 | ||||
-rw-r--r-- | net/bridge/br_notify.c | 2 | ||||
-rw-r--r-- | net/bridge/br_private.h | 14 | ||||
-rw-r--r-- | net/bridge/br_stp_bpdu.c | 8 | ||||
-rw-r--r-- | net/bridge/netfilter/ebtables.c | 11 |
8 files changed, 44 insertions, 34 deletions
diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index 90512ccfd3e9..2872393b2939 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c | |||
@@ -238,15 +238,18 @@ struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br, | |||
238 | int br_fdb_test_addr(struct net_device *dev, unsigned char *addr) | 238 | int br_fdb_test_addr(struct net_device *dev, unsigned char *addr) |
239 | { | 239 | { |
240 | struct net_bridge_fdb_entry *fdb; | 240 | struct net_bridge_fdb_entry *fdb; |
241 | struct net_bridge_port *port; | ||
241 | int ret; | 242 | int ret; |
242 | 243 | ||
243 | if (!br_port_exists(dev)) | ||
244 | return 0; | ||
245 | |||
246 | rcu_read_lock(); | 244 | rcu_read_lock(); |
247 | fdb = __br_fdb_get(br_port_get_rcu(dev)->br, addr); | 245 | port = br_port_get_rcu(dev); |
248 | ret = fdb && fdb->dst->dev != dev && | 246 | if (!port) |
249 | fdb->dst->state == BR_STATE_FORWARDING; | 247 | ret = 0; |
248 | else { | ||
249 | fdb = __br_fdb_get(port->br, addr); | ||
250 | ret = fdb && fdb->dst->dev != dev && | ||
251 | fdb->dst->state == BR_STATE_FORWARDING; | ||
252 | } | ||
250 | rcu_read_unlock(); | 253 | rcu_read_unlock(); |
251 | 254 | ||
252 | return ret; | 255 | return ret; |
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c index 89ad25a76202..427f90a8ab7b 100644 --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c | |||
@@ -475,11 +475,8 @@ int br_del_if(struct net_bridge *br, struct net_device *dev) | |||
475 | { | 475 | { |
476 | struct net_bridge_port *p; | 476 | struct net_bridge_port *p; |
477 | 477 | ||
478 | if (!br_port_exists(dev)) | ||
479 | return -EINVAL; | ||
480 | |||
481 | p = br_port_get(dev); | 478 | p = br_port_get(dev); |
482 | if (p->br != br) | 479 | if (!p || p->br != br) |
483 | return -EINVAL; | 480 | return -EINVAL; |
484 | 481 | ||
485 | del_nbp(p); | 482 | del_nbp(p); |
diff --git a/net/bridge/br_netfilter.c b/net/bridge/br_netfilter.c index 865fd7634b67..ce8b2eed4e73 100644 --- a/net/bridge/br_netfilter.c +++ b/net/bridge/br_netfilter.c | |||
@@ -131,17 +131,18 @@ void br_netfilter_rtable_init(struct net_bridge *br) | |||
131 | 131 | ||
132 | static inline struct rtable *bridge_parent_rtable(const struct net_device *dev) | 132 | static inline struct rtable *bridge_parent_rtable(const struct net_device *dev) |
133 | { | 133 | { |
134 | if (!br_port_exists(dev)) | 134 | struct net_bridge_port *port; |
135 | return NULL; | 135 | |
136 | return &br_port_get_rcu(dev)->br->fake_rtable; | 136 | port = br_port_get_rcu(dev); |
137 | return port ? &port->br->fake_rtable : NULL; | ||
137 | } | 138 | } |
138 | 139 | ||
139 | static inline struct net_device *bridge_parent(const struct net_device *dev) | 140 | static inline struct net_device *bridge_parent(const struct net_device *dev) |
140 | { | 141 | { |
141 | if (!br_port_exists(dev)) | 142 | struct net_bridge_port *port; |
142 | return NULL; | ||
143 | 143 | ||
144 | return br_port_get_rcu(dev)->br->dev; | 144 | port = br_port_get_rcu(dev); |
145 | return port ? port->br->dev : NULL; | ||
145 | } | 146 | } |
146 | 147 | ||
147 | static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb) | 148 | static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb) |
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c index 4a6a378c84e3..e3de0a428f5d 100644 --- a/net/bridge/br_netlink.c +++ b/net/bridge/br_netlink.c | |||
@@ -119,11 +119,13 @@ static int br_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb) | |||
119 | 119 | ||
120 | idx = 0; | 120 | idx = 0; |
121 | for_each_netdev(net, dev) { | 121 | for_each_netdev(net, dev) { |
122 | struct net_bridge_port *port = br_port_get(dev); | ||
123 | |||
122 | /* not a bridge port */ | 124 | /* not a bridge port */ |
123 | if (!br_port_exists(dev) || idx < cb->args[0]) | 125 | if (!port || idx < cb->args[0]) |
124 | goto skip; | 126 | goto skip; |
125 | 127 | ||
126 | if (br_fill_ifinfo(skb, br_port_get(dev), | 128 | if (br_fill_ifinfo(skb, port, |
127 | NETLINK_CB(cb->skb).pid, | 129 | NETLINK_CB(cb->skb).pid, |
128 | cb->nlh->nlmsg_seq, RTM_NEWLINK, | 130 | cb->nlh->nlmsg_seq, RTM_NEWLINK, |
129 | NLM_F_MULTI) < 0) | 131 | NLM_F_MULTI) < 0) |
@@ -169,9 +171,9 @@ static int br_rtm_setlink(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) | |||
169 | if (!dev) | 171 | if (!dev) |
170 | return -ENODEV; | 172 | return -ENODEV; |
171 | 173 | ||
172 | if (!br_port_exists(dev)) | ||
173 | return -EINVAL; | ||
174 | p = br_port_get(dev); | 174 | p = br_port_get(dev); |
175 | if (!p) | ||
176 | return -EINVAL; | ||
175 | 177 | ||
176 | /* if kernel STP is running, don't allow changes */ | 178 | /* if kernel STP is running, don't allow changes */ |
177 | if (p->br->stp_enabled == BR_KERNEL_STP) | 179 | if (p->br->stp_enabled == BR_KERNEL_STP) |
diff --git a/net/bridge/br_notify.c b/net/bridge/br_notify.c index 404d4e14c6a7..ef2175c8b91d 100644 --- a/net/bridge/br_notify.c +++ b/net/bridge/br_notify.c | |||
@@ -32,7 +32,7 @@ struct notifier_block br_device_notifier = { | |||
32 | static int br_device_event(struct notifier_block *unused, unsigned long event, void *ptr) | 32 | static int br_device_event(struct notifier_block *unused, unsigned long event, void *ptr) |
33 | { | 33 | { |
34 | struct net_device *dev = ptr; | 34 | struct net_device *dev = ptr; |
35 | struct net_bridge_port *p = br_port_get(dev); | 35 | struct net_bridge_port *p; |
36 | struct net_bridge *br; | 36 | struct net_bridge *br; |
37 | int err; | 37 | int err; |
38 | 38 | ||
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index b862071bf601..46e0bec1d7c5 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h | |||
@@ -151,11 +151,19 @@ struct net_bridge_port | |||
151 | #endif | 151 | #endif |
152 | }; | 152 | }; |
153 | 153 | ||
154 | #define br_port_get_rcu(dev) \ | ||
155 | ((struct net_bridge_port *) rcu_dereference(dev->rx_handler_data)) | ||
156 | #define br_port_get(dev) ((struct net_bridge_port *) dev->rx_handler_data) | ||
157 | #define br_port_exists(dev) (dev->priv_flags & IFF_BRIDGE_PORT) | 154 | #define br_port_exists(dev) (dev->priv_flags & IFF_BRIDGE_PORT) |
158 | 155 | ||
156 | static inline struct net_bridge_port *br_port_get_rcu(const struct net_device *dev) | ||
157 | { | ||
158 | struct net_bridge_port *port = rcu_dereference(dev->rx_handler_data); | ||
159 | return br_port_exists(dev) ? port : NULL; | ||
160 | } | ||
161 | |||
162 | static inline struct net_bridge_port *br_port_get(struct net_device *dev) | ||
163 | { | ||
164 | return br_port_exists(dev) ? dev->rx_handler_data : NULL; | ||
165 | } | ||
166 | |||
159 | struct br_cpu_netstats { | 167 | struct br_cpu_netstats { |
160 | u64 rx_packets; | 168 | u64 rx_packets; |
161 | u64 rx_bytes; | 169 | u64 rx_bytes; |
diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c index 35cf27087b56..3d9a55d3822f 100644 --- a/net/bridge/br_stp_bpdu.c +++ b/net/bridge/br_stp_bpdu.c | |||
@@ -141,10 +141,6 @@ void br_stp_rcv(const struct stp_proto *proto, struct sk_buff *skb, | |||
141 | struct net_bridge *br; | 141 | struct net_bridge *br; |
142 | const unsigned char *buf; | 142 | const unsigned char *buf; |
143 | 143 | ||
144 | if (!br_port_exists(dev)) | ||
145 | goto err; | ||
146 | p = br_port_get_rcu(dev); | ||
147 | |||
148 | if (!pskb_may_pull(skb, 4)) | 144 | if (!pskb_may_pull(skb, 4)) |
149 | goto err; | 145 | goto err; |
150 | 146 | ||
@@ -153,6 +149,10 @@ void br_stp_rcv(const struct stp_proto *proto, struct sk_buff *skb, | |||
153 | if (buf[0] != 0 || buf[1] != 0 || buf[2] != 0) | 149 | if (buf[0] != 0 || buf[1] != 0 || buf[2] != 0) |
154 | goto err; | 150 | goto err; |
155 | 151 | ||
152 | p = br_port_get_rcu(dev); | ||
153 | if (!p) | ||
154 | goto err; | ||
155 | |||
156 | br = p->br; | 156 | br = p->br; |
157 | spin_lock(&br->lock); | 157 | spin_lock(&br->lock); |
158 | 158 | ||
diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index a1dcf83f0d58..cbc9f395ab1e 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c | |||
@@ -128,6 +128,7 @@ ebt_basic_match(const struct ebt_entry *e, const struct sk_buff *skb, | |||
128 | const struct net_device *in, const struct net_device *out) | 128 | const struct net_device *in, const struct net_device *out) |
129 | { | 129 | { |
130 | const struct ethhdr *h = eth_hdr(skb); | 130 | const struct ethhdr *h = eth_hdr(skb); |
131 | const struct net_bridge_port *p; | ||
131 | __be16 ethproto; | 132 | __be16 ethproto; |
132 | int verdict, i; | 133 | int verdict, i; |
133 | 134 | ||
@@ -148,13 +149,11 @@ ebt_basic_match(const struct ebt_entry *e, const struct sk_buff *skb, | |||
148 | if (FWINV2(ebt_dev_check(e->out, out), EBT_IOUT)) | 149 | if (FWINV2(ebt_dev_check(e->out, out), EBT_IOUT)) |
149 | return 1; | 150 | return 1; |
150 | /* rcu_read_lock()ed by nf_hook_slow */ | 151 | /* rcu_read_lock()ed by nf_hook_slow */ |
151 | if (in && br_port_exists(in) && | 152 | if (in && (p = br_port_get_rcu(in)) != NULL && |
152 | FWINV2(ebt_dev_check(e->logical_in, br_port_get_rcu(in)->br->dev), | 153 | FWINV2(ebt_dev_check(e->logical_in, p->br->dev), EBT_ILOGICALIN)) |
153 | EBT_ILOGICALIN)) | ||
154 | return 1; | 154 | return 1; |
155 | if (out && br_port_exists(out) && | 155 | if (out && (p = br_port_get_rcu(out)) != NULL && |
156 | FWINV2(ebt_dev_check(e->logical_out, br_port_get_rcu(out)->br->dev), | 156 | FWINV2(ebt_dev_check(e->logical_out, p->br->dev), EBT_ILOGICALOUT)) |
157 | EBT_ILOGICALOUT)) | ||
158 | return 1; | 157 | return 1; |
159 | 158 | ||
160 | if (e->bitmask & EBT_SOURCEMAC) { | 159 | if (e->bitmask & EBT_SOURCEMAC) { |