diff options
| author | Eric Dumazet <eric.dumazet@gmail.com> | 2011-04-21 09:45:37 +0000 | 
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2011-04-28 13:16:35 -0700 | 
| commit | f6d8bd051c391c1c0458a30b2a7abcd939329259 (patch) | |
| tree | 1dc4daecdeb0b42c2c6b59d7d6b41e091c11db5f /net/ipv4/raw.c | |
| parent | 0a14842f5a3c0e88a1e59fac5c3025db39721f74 (diff) | |
| download | olio-linux-3.10-f6d8bd051c391c1c0458a30b2a7abcd939329259.tar.xz olio-linux-3.10-f6d8bd051c391c1c0458a30b2a7abcd939329259.zip  | |
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.c | 19 | 
1 files changed, 15 insertions, 4 deletions
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index abf14dbcb3b..a8659e0c4a6 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,  	__be32 saddr;  	u8  tos;  	int err; +	struct ip_options_data opt_copy;  	err = -EMSGSIZE;  	if (len > 0xFFFF) @@ -520,8 +521,18 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,  	saddr = ipc.addr;  	ipc.addr = daddr; -	if (!ipc.opt) -		ipc.opt = inet->opt; +	if (!ipc.opt) { +		struct ip_options_rcu *inet_opt; + +		rcu_read_lock(); +		inet_opt = rcu_dereference(inet->inet_opt); +		if (inet_opt) { +			memcpy(&opt_copy, inet_opt, +			       sizeof(*inet_opt) + inet_opt->opt.optlen); +			ipc.opt = &opt_copy.opt; +		} +		rcu_read_unlock(); +	}  	if (ipc.opt) {  		err = -EINVAL; @@ -530,10 +541,10 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,  		 */  		if (inet->hdrincl)  			goto done; -		if (ipc.opt->srr) { +		if (ipc.opt->opt.srr) {  			if (!daddr)  				goto done; -			daddr = ipc.opt->faddr; +			daddr = ipc.opt->opt.faddr;  		}  	}  	tos = RT_CONN_FLAGS(sk);  |