aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv4/raw.c
diff options
context:
space:
mode:
authorEric Dumazet <eric.dumazet@gmail.com>2011-04-21 05:45:37 -0400
committerDavid S. Miller <davem@davemloft.net>2011-04-28 16:16:35 -0400
commitf6d8bd051c391c1c0458a30b2a7abcd939329259 (patch)
tree1dc4daecdeb0b42c2c6b59d7d6b41e091c11db5f /net/ipv4/raw.c
parent0a14842f5a3c0e88a1e59fac5c3025db39721f74 (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/ipv4/raw.c')
-rw-r--r--net/ipv4/raw.c19
1 files changed, 15 insertions, 4 deletions
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
index abf14dbcb3b9..a8659e0c4a6e 100644
--- a/net/ipv4/raw.c
+++ b/net/ipv4/raw.c
@@ -460,6 +460,7 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
460 __be32 saddr; 460 __be32 saddr;
461 u8 tos; 461 u8 tos;
462 int err; 462 int err;
463 struct ip_options_data opt_copy;
463 464
464 err = -EMSGSIZE; 465 err = -EMSGSIZE;
465 if (len > 0xFFFF) 466 if (len > 0xFFFF)
@@ -520,8 +521,18 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
520 saddr = ipc.addr; 521 saddr = ipc.addr;
521 ipc.addr = daddr; 522 ipc.addr = daddr;
522 523
523 if (!ipc.opt) 524 if (!ipc.opt) {
524 ipc.opt = inet->opt; 525 struct ip_options_rcu *inet_opt;
526
527 rcu_read_lock();
528 inet_opt = rcu_dereference(inet->inet_opt);
529 if (inet_opt) {
530 memcpy(&opt_copy, inet_opt,
531 sizeof(*inet_opt) + inet_opt->opt.optlen);
532 ipc.opt = &opt_copy.opt;
533 }
534 rcu_read_unlock();
535 }
525 536
526 if (ipc.opt) { 537 if (ipc.opt) {
527 err = -EINVAL; 538 err = -EINVAL;
@@ -530,10 +541,10 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
530 */ 541 */
531 if (inet->hdrincl) 542 if (inet->hdrincl)
532 goto done; 543 goto done;
533 if (ipc.opt->srr) { 544 if (ipc.opt->opt.srr) {
534 if (!daddr) 545 if (!daddr)
535 goto done; 546 goto done;
536 daddr = ipc.opt->faddr; 547 daddr = ipc.opt->opt.faddr;
537 } 548 }
538 } 549 }
539 tos = RT_CONN_FLAGS(sk); 550 tos = RT_CONN_FLAGS(sk);