ipv6: add complete rcu protection around np->opt
This patch addresses multiple problems : UDP/RAW sendmsg() need to get a stable struct ipv6_txoptions while socket is not locked : Other threads can change np->opt concurrently. Dmitry posted a syzkaller (http://github.com/google/syzkaller) program desmonstrating use-after-free. Starting with TCP/DCCP lockless listeners, tcp_v6_syn_recv_sock() and dccp_v6_request_recv_sock() also need to use RCU protection to dereference np->opt once (before calling ipv6_dup_options()) This patch adds full RCU protection to np->opt Reported-by: Dmitry Vyukov <dvyukov@google.com> Signed-off-by: Eric Dumazet <edumazet@google.com> Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:

committed by
David S. Miller

parent
01b3f52157
commit
45f6fad84c
@@ -120,6 +120,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
|
||||
struct ipv6_pinfo *np = inet6_sk(sk);
|
||||
struct tcp_sock *tp = tcp_sk(sk);
|
||||
struct in6_addr *saddr = NULL, *final_p, final;
|
||||
struct ipv6_txoptions *opt;
|
||||
struct flowi6 fl6;
|
||||
struct dst_entry *dst;
|
||||
int addr_type;
|
||||
@@ -235,7 +236,8 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
|
||||
fl6.fl6_dport = usin->sin6_port;
|
||||
fl6.fl6_sport = inet->inet_sport;
|
||||
|
||||
final_p = fl6_update_dst(&fl6, np->opt, &final);
|
||||
opt = rcu_dereference_protected(np->opt, sock_owned_by_user(sk));
|
||||
final_p = fl6_update_dst(&fl6, opt, &final);
|
||||
|
||||
security_sk_classify_flow(sk, flowi6_to_flowi(&fl6));
|
||||
|
||||
@@ -263,9 +265,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
|
||||
tcp_fetch_timewait_stamp(sk, dst);
|
||||
|
||||
icsk->icsk_ext_hdr_len = 0;
|
||||
if (np->opt)
|
||||
icsk->icsk_ext_hdr_len = (np->opt->opt_flen +
|
||||
np->opt->opt_nflen);
|
||||
if (opt)
|
||||
icsk->icsk_ext_hdr_len = opt->opt_flen +
|
||||
opt->opt_nflen;
|
||||
|
||||
tp->rx_opt.mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr);
|
||||
|
||||
@@ -461,7 +463,8 @@ static int tcp_v6_send_synack(const struct sock *sk, struct dst_entry *dst,
|
||||
if (np->repflow && ireq->pktopts)
|
||||
fl6->flowlabel = ip6_flowlabel(ipv6_hdr(ireq->pktopts));
|
||||
|
||||
err = ip6_xmit(sk, skb, fl6, np->opt, np->tclass);
|
||||
err = ip6_xmit(sk, skb, fl6, rcu_dereference(np->opt),
|
||||
np->tclass);
|
||||
err = net_xmit_eval(err);
|
||||
}
|
||||
|
||||
@@ -972,6 +975,7 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *
|
||||
struct inet_request_sock *ireq;
|
||||
struct ipv6_pinfo *newnp;
|
||||
const struct ipv6_pinfo *np = inet6_sk(sk);
|
||||
struct ipv6_txoptions *opt;
|
||||
struct tcp6_sock *newtcp6sk;
|
||||
struct inet_sock *newinet;
|
||||
struct tcp_sock *newtp;
|
||||
@@ -1098,13 +1102,15 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *
|
||||
but we make one more one thing there: reattach optmem
|
||||
to newsk.
|
||||
*/
|
||||
if (np->opt)
|
||||
newnp->opt = ipv6_dup_options(newsk, np->opt);
|
||||
|
||||
opt = rcu_dereference(np->opt);
|
||||
if (opt) {
|
||||
opt = ipv6_dup_options(newsk, opt);
|
||||
RCU_INIT_POINTER(newnp->opt, opt);
|
||||
}
|
||||
inet_csk(newsk)->icsk_ext_hdr_len = 0;
|
||||
if (newnp->opt)
|
||||
inet_csk(newsk)->icsk_ext_hdr_len = (newnp->opt->opt_nflen +
|
||||
newnp->opt->opt_flen);
|
||||
if (opt)
|
||||
inet_csk(newsk)->icsk_ext_hdr_len = opt->opt_nflen +
|
||||
opt->opt_flen;
|
||||
|
||||
tcp_ca_openreq_child(newsk, dst);
|
||||
|
||||
|
Reference in New Issue
Block a user