diff options
| author | Eric Dumazet <eric.dumazet@gmail.com> | 2011-04-21 05:45:37 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2011-04-28 16:16:35 -0400 |
| commit | f6d8bd051c391c1c0458a30b2a7abcd939329259 (patch) | |
| tree | 1dc4daecdeb0b42c2c6b59d7d6b41e091c11db5f /net/dccp | |
| parent | 0a14842f5a3c0e88a1e59fac5c3025db39721f74 (diff) | |
inet: add RCU protection to inet->opt
We lack proper synchronization to manipulate inet->opt ip_options
Problem is ip_make_skb() calls ip_setup_cork() and
ip_setup_cork() possibly makes a copy of ipc->opt (struct ip_options),
without any protection against another thread manipulating inet->opt.
Another thread can change inet->opt pointer and free old one under us.
Use RCU to protect inet->opt (changed to inet->inet_opt).
Instead of handling atomic refcounts, just copy ip_options when
necessary, to avoid cache line dirtying.
We cant insert an rcu_head in struct ip_options since its included in
skb->cb[], so this patch is large because I had to introduce a new
ip_options_rcu structure.
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/dccp')
| -rw-r--r-- | net/dccp/ipv4.c | 16 | ||||
| -rw-r--r-- | net/dccp/ipv6.c | 2 |
2 files changed, 11 insertions, 7 deletions
diff --git a/net/dccp/ipv4.c b/net/dccp/ipv4.c index b92ab655d44..cbbcc6c036e 100644 --- a/net/dccp/ipv4.c +++ b/net/dccp/ipv4.c | |||
| @@ -48,6 +48,7 @@ int dccp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) | |||
| 48 | struct flowi4 fl4; | 48 | struct flowi4 fl4; |
| 49 | struct rtable *rt; | 49 | struct rtable *rt; |
| 50 | int err; | 50 | int err; |
| 51 | struct ip_options_rcu *inet_opt; | ||
| 51 | 52 | ||
| 52 | dp->dccps_role = DCCP_ROLE_CLIENT; | 53 | dp->dccps_role = DCCP_ROLE_CLIENT; |
| 53 | 54 | ||
| @@ -58,10 +59,13 @@ int dccp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) | |||
| 58 | return -EAFNOSUPPORT; | 59 | return -EAFNOSUPPORT; |
| 59 | 60 | ||
| 60 | nexthop = daddr = usin->sin_addr.s_addr; | 61 | nexthop = daddr = usin->sin_addr.s_addr; |
| 61 | if (inet->opt != NULL && inet->opt->srr) { | 62 | |
| 63 | inet_opt = rcu_dereference_protected(inet->inet_opt, | ||
| 64 | sock_owned_by_user(sk)); | ||
| 65 | if (inet_opt != NULL && inet_opt->opt.srr) { | ||
| 62 | if (daddr == 0) | 66 | if (daddr == 0) |
| 63 | return -EINVAL; | 67 | return -EINVAL; |
| 64 | nexthop = inet->opt->faddr; | 68 | nexthop = inet_opt->opt.faddr; |
| 65 | } | 69 | } |
| 66 | 70 | ||
| 67 | orig_sport = inet->inet_sport; | 71 | orig_sport = inet->inet_sport; |
| @@ -78,7 +82,7 @@ int dccp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) | |||
| 78 | return -ENETUNREACH; | 82 | return -ENETUNREACH; |
| 79 | } | 83 | } |
| 80 | 84 | ||
| 81 | if (inet->opt == NULL || !inet->opt->srr) | 85 | if (inet_opt == NULL || !inet_opt->opt.srr) |
| 82 | daddr = rt->rt_dst; | 86 | daddr = rt->rt_dst; |
| 83 | 87 | ||
| 84 | if (inet->inet_saddr == 0) | 88 | if (inet->inet_saddr == 0) |
| @@ -89,8 +93,8 @@ int dccp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) | |||
| 89 | inet->inet_daddr = daddr; | 93 | inet->inet_daddr = daddr; |
| 90 | 94 | ||
| 91 | inet_csk(sk)->icsk_ext_hdr_len = 0; | 95 | inet_csk(sk)->icsk_ext_hdr_len = 0; |
| 92 | if (inet->opt != NULL) | 96 | if (inet_opt) |
| 93 | inet_csk(sk)->icsk_ext_hdr_len = inet->opt->optlen; | 97 | inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; |
| 94 | /* | 98 | /* |
| 95 | * Socket identity is still unknown (sport may be zero). | 99 | * Socket identity is still unknown (sport may be zero). |
| 96 | * However we set state to DCCP_REQUESTING and not releasing socket | 100 | * However we set state to DCCP_REQUESTING and not releasing socket |
| @@ -405,7 +409,7 @@ struct sock *dccp_v4_request_recv_sock(struct sock *sk, struct sk_buff *skb, | |||
| 405 | newinet->inet_daddr = ireq->rmt_addr; | 409 | newinet->inet_daddr = ireq->rmt_addr; |
| 406 | newinet->inet_rcv_saddr = ireq->loc_addr; | 410 | newinet->inet_rcv_saddr = ireq->loc_addr; |
| 407 | newinet->inet_saddr = ireq->loc_addr; | 411 | newinet->inet_saddr = ireq->loc_addr; |
| 408 | newinet->opt = ireq->opt; | 412 | newinet->inet_opt = ireq->opt; |
| 409 | ireq->opt = NULL; | 413 | ireq->opt = NULL; |
| 410 | newinet->mc_index = inet_iif(skb); | 414 | newinet->mc_index = inet_iif(skb); |
| 411 | newinet->mc_ttl = ip_hdr(skb)->ttl; | 415 | newinet->mc_ttl = ip_hdr(skb)->ttl; |
diff --git a/net/dccp/ipv6.c b/net/dccp/ipv6.c index 73add237324..8dc4348774a 100644 --- a/net/dccp/ipv6.c +++ b/net/dccp/ipv6.c | |||
| @@ -573,7 +573,7 @@ static struct sock *dccp_v6_request_recv_sock(struct sock *sk, | |||
| 573 | 573 | ||
| 574 | First: no IPv4 options. | 574 | First: no IPv4 options. |
| 575 | */ | 575 | */ |
| 576 | newinet->opt = NULL; | 576 | newinet->inet_opt = NULL; |
| 577 | 577 | ||
| 578 | /* Clone RX bits */ | 578 | /* Clone RX bits */ |
| 579 | newnp->rxopt.all = np->rxopt.all; | 579 | newnp->rxopt.all = np->rxopt.all; |
