summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Dumazet <eric.dumazet@gmail.com>2011-06-12 02:27:09 +0400
committerDavid S. Miller <davem@conan.davemloft.net>2011-06-14 01:31:30 +0400
commit081b1b1bb27f1f4a3b682f4cf75103108f2066d7 (patch)
tree6b46eac808fe4292bff144ff0b5e6461ef957810
parentec1d7c9a5e9c136e08a9b1df3109ad5d38d8179c (diff)
downloadlinux-081b1b1bb27f1f4a3b682f4cf75103108f2066d7.tar.xz
l2tp: fix l2tp_ip_sendmsg() route handling
l2tp_ip_sendmsg() in non connected mode incorrectly calls sk_setup_caps(). Subsequent send() calls send data to wrong destination. We can also avoid changing dst refcount in connected mode, using appropriate rcu locking. Once output route lookups can also be done under rcu, sendto() calls wont change dst refcounts too. Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com> CC: James Chapman <jchapman@katalix.com> Signed-off-by: David S. Miller <davem@conan.davemloft.net>
-rw-r--r--net/l2tp/l2tp_ip.c19
1 files changed, 13 insertions, 6 deletions
diff --git a/net/l2tp/l2tp_ip.c b/net/l2tp/l2tp_ip.c
index b6466e71f5e1..d21e7ebd91ca 100644
--- a/net/l2tp/l2tp_ip.c
+++ b/net/l2tp/l2tp_ip.c
@@ -480,18 +480,16 @@ static int l2tp_ip_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *m
if (connected)
rt = (struct rtable *) __sk_dst_check(sk, 0);
+ rcu_read_lock();
if (rt == NULL) {
- struct ip_options_rcu *inet_opt;
+ const struct ip_options_rcu *inet_opt;
- rcu_read_lock();
inet_opt = rcu_dereference(inet->inet_opt);
/* Use correct destination address if we have options. */
if (inet_opt && inet_opt->opt.srr)
daddr = inet_opt->opt.faddr;
- rcu_read_unlock();
-
/* If this fails, retransmit mechanism of transport layer will
* keep trying until route appears or the connection times
* itself out.
@@ -503,12 +501,20 @@ static int l2tp_ip_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *m
sk->sk_bound_dev_if);
if (IS_ERR(rt))
goto no_route;
- sk_setup_caps(sk, &rt->dst);
+ if (connected)
+ sk_setup_caps(sk, &rt->dst);
+ else
+ dst_release(&rt->dst); /* safe since we hold rcu_read_lock */
}
- skb_dst_set(skb, dst_clone(&rt->dst));
+
+ /* We dont need to clone dst here, it is guaranteed to not disappear.
+ * __dev_xmit_skb() might force a refcount if needed.
+ */
+ skb_dst_set_noref(skb, &rt->dst);
/* Queue the packet to IP for output */
rc = ip_queue_xmit(skb, &inet->cork.fl);
+ rcu_read_unlock();
error:
/* Update stats */
@@ -525,6 +531,7 @@ out:
return rc;
no_route:
+ rcu_read_unlock();
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
kfree_skb(skb);
rc = -EHOSTUNREACH;