diff options
author | Joe Stringer <joestringer@nicira.com> | 2015-10-25 23:21:48 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2015-10-27 22:32:14 -0400 |
commit | 74c16618137f1505b0a32dea3ec73a2ef6f8f842 (patch) | |
tree | a85b6a44ec0f5d8d7ae915968ee88250c323a292 | |
parent | c2229fe1430d4e1c70e36520229dd64a87802b20 (diff) |
openvswitch: Fix double-free on ip_defrag() errors
If ip_defrag() returns an error other than -EINPROGRESS, then the skb is
freed. When handle_fragments() passes this back up to
do_execute_actions(), it will be freed again. Prevent this double free
by never freeing the skb in do_execute_actions() for errors returned by
ovs_ct_execute. Always free it in ovs_ct_execute() error paths instead.
Fixes: 7f8a436eaa2c ("openvswitch: Add conntrack action")
Reported-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Joe Stringer <joestringer@nicira.com>
Acked-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | net/openvswitch/actions.c | 4 | ||||
-rw-r--r-- | net/openvswitch/conntrack.c | 17 | ||||
-rw-r--r-- | net/openvswitch/conntrack.h | 1 |
3 files changed, 16 insertions, 6 deletions
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index 0bf0f406de52..dba635d086b2 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c | |||
@@ -1109,8 +1109,8 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, | |||
1109 | nla_data(a)); | 1109 | nla_data(a)); |
1110 | 1110 | ||
1111 | /* Hide stolen IP fragments from user space. */ | 1111 | /* Hide stolen IP fragments from user space. */ |
1112 | if (err == -EINPROGRESS) | 1112 | if (err) |
1113 | return 0; | 1113 | return err == -EINPROGRESS ? 0 : err; |
1114 | break; | 1114 | break; |
1115 | } | 1115 | } |
1116 | 1116 | ||
diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index a5ec34f8502f..b5dcc0abde66 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c | |||
@@ -293,6 +293,9 @@ static int ovs_ct_helper(struct sk_buff *skb, u16 proto) | |||
293 | return helper->help(skb, protoff, ct, ctinfo); | 293 | return helper->help(skb, protoff, ct, ctinfo); |
294 | } | 294 | } |
295 | 295 | ||
296 | /* Returns 0 on success, -EINPROGRESS if 'skb' is stolen, or other nonzero | ||
297 | * value if 'skb' is freed. | ||
298 | */ | ||
296 | static int handle_fragments(struct net *net, struct sw_flow_key *key, | 299 | static int handle_fragments(struct net *net, struct sw_flow_key *key, |
297 | u16 zone, struct sk_buff *skb) | 300 | u16 zone, struct sk_buff *skb) |
298 | { | 301 | { |
@@ -308,8 +311,8 @@ static int handle_fragments(struct net *net, struct sw_flow_key *key, | |||
308 | return err; | 311 | return err; |
309 | 312 | ||
310 | ovs_cb.mru = IPCB(skb)->frag_max_size; | 313 | ovs_cb.mru = IPCB(skb)->frag_max_size; |
311 | } else if (key->eth.type == htons(ETH_P_IPV6)) { | ||
312 | #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) | 314 | #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) |
315 | } else if (key->eth.type == htons(ETH_P_IPV6)) { | ||
313 | enum ip6_defrag_users user = IP6_DEFRAG_CONNTRACK_IN + zone; | 316 | enum ip6_defrag_users user = IP6_DEFRAG_CONNTRACK_IN + zone; |
314 | struct sk_buff *reasm; | 317 | struct sk_buff *reasm; |
315 | 318 | ||
@@ -318,17 +321,18 @@ static int handle_fragments(struct net *net, struct sw_flow_key *key, | |||
318 | if (!reasm) | 321 | if (!reasm) |
319 | return -EINPROGRESS; | 322 | return -EINPROGRESS; |
320 | 323 | ||
321 | if (skb == reasm) | 324 | if (skb == reasm) { |
325 | kfree_skb(skb); | ||
322 | return -EINVAL; | 326 | return -EINVAL; |
327 | } | ||
323 | 328 | ||
324 | key->ip.proto = ipv6_hdr(reasm)->nexthdr; | 329 | key->ip.proto = ipv6_hdr(reasm)->nexthdr; |
325 | skb_morph(skb, reasm); | 330 | skb_morph(skb, reasm); |
326 | consume_skb(reasm); | 331 | consume_skb(reasm); |
327 | ovs_cb.mru = IP6CB(skb)->frag_max_size; | 332 | ovs_cb.mru = IP6CB(skb)->frag_max_size; |
328 | #else | ||
329 | return -EPFNOSUPPORT; | ||
330 | #endif | 333 | #endif |
331 | } else { | 334 | } else { |
335 | kfree_skb(skb); | ||
332 | return -EPFNOSUPPORT; | 336 | return -EPFNOSUPPORT; |
333 | } | 337 | } |
334 | 338 | ||
@@ -473,6 +477,9 @@ static bool labels_nonzero(const struct ovs_key_ct_labels *labels) | |||
473 | return false; | 477 | return false; |
474 | } | 478 | } |
475 | 479 | ||
480 | /* Returns 0 on success, -EINPROGRESS if 'skb' is stolen, or other nonzero | ||
481 | * value if 'skb' is freed. | ||
482 | */ | ||
476 | int ovs_ct_execute(struct net *net, struct sk_buff *skb, | 483 | int ovs_ct_execute(struct net *net, struct sk_buff *skb, |
477 | struct sw_flow_key *key, | 484 | struct sw_flow_key *key, |
478 | const struct ovs_conntrack_info *info) | 485 | const struct ovs_conntrack_info *info) |
@@ -508,6 +515,8 @@ int ovs_ct_execute(struct net *net, struct sk_buff *skb, | |||
508 | &info->labels.mask); | 515 | &info->labels.mask); |
509 | err: | 516 | err: |
510 | skb_push(skb, nh_ofs); | 517 | skb_push(skb, nh_ofs); |
518 | if (err) | ||
519 | kfree_skb(skb); | ||
511 | return err; | 520 | return err; |
512 | } | 521 | } |
513 | 522 | ||
diff --git a/net/openvswitch/conntrack.h b/net/openvswitch/conntrack.h index 82e0dfc66028..a7544f405c16 100644 --- a/net/openvswitch/conntrack.h +++ b/net/openvswitch/conntrack.h | |||
@@ -67,6 +67,7 @@ static inline int ovs_ct_execute(struct net *net, struct sk_buff *skb, | |||
67 | struct sw_flow_key *key, | 67 | struct sw_flow_key *key, |
68 | const struct ovs_conntrack_info *info) | 68 | const struct ovs_conntrack_info *info) |
69 | { | 69 | { |
70 | kfree_skb(skb); | ||
70 | return -ENOTSUPP; | 71 | return -ENOTSUPP; |
71 | } | 72 | } |
72 | 73 | ||