Patch "ipv6: add complete rcu protection around np->opt" has been added to the 4.2-stable tree
Subsystems (5)
This is a note to let you know that I've just added the patch titled
ipv6: add complete rcu protection around np->opt
to the 4.2-stable tree which can be found at:
http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary
The filename of the patch is:
ipv6-add-complete-rcu-protection-around-np-opt.patch
and it can be found in the queue-4.2 subdirectory.
If you, or anyone else, feels it should not be added to the stable tree, please let <[email protected]> know about it.
Quoted 1 line
From foo@baz Fri Dec 11 11:38:35 EST 2015
From: Eric Dumazet <[email protected]>
Date: Sun, 29 Nov 2015 19:37:57 -0800
Subject: ipv6: add complete rcu protection around np->opt
From: Eric Dumazet <[email protected]>
[ Upstream commit 45f6fad84cc305103b28d73482b344d7f5b76f39 ]
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
--- include/linux/ipv6.h | 2 +- include/net/ipv6.h | 21 ++++++++++++++++++++- net/dccp/ipv6.c | 33 +++++++++++++++++++++------------ net/ipv6/af_inet6.c | 13 +++++++++---- net/ipv6/datagram.c | 4 +++- net/ipv6/exthdrs.c | 3 ++- net/ipv6/inet6_connection_sock.c | 11 ++++++++--- net/ipv6/ipv6_sockglue.c | 33 ++++++++++++++++++++++----------- net/ipv6/raw.c | 8 ++++++-- net/ipv6/syncookies.c | 2 +- net/ipv6/tcp_ipv6.c | 28 +++++++++++++++++----------- net/ipv6/udp.c | 8 ++++++-- net/l2tp/l2tp_ip6.c | 8 ++++++-- 13 files changed, 122 insertions(+), 52 deletions(-)
include/linux/ipv6.h +1 -1
@@ -224,7 +224,7 @@ struct ipv6_pinfo {struct ipv6_ac_socklist *ipv6_ac_list;struct ipv6_fl_socklist __rcu *ipv6_fl_list; -struct ipv6_txoptions *opt; +struct ipv6_txoptions __rcu *opt;struct sk_buff *pktoptions;struct sk_buff *rxpmtu;struct inet6_cork cork;
include/net/ipv6.h +20 -1
@@ -205,6 +205,7 @@ extern rwlock_t ip6_ra_lock;*/ struct ipv6_txoptions { +atomic_t refcnt;/* Length of this structure */ int tot_len;
@@ -217,7 +218,7 @@ struct ipv6_txoptions {struct ipv6_opt_hdr *dst0opt;struct ipv6_rt_hdr *srcrt;/* Routing Header */ struct ipv6_opt_hdr *dst1opt; - +struct rcu_head rcu;/* Option buffer, as read by IPV6_PKTOPTIONS, starts here. */ };
@@ -252,6 +253,24 @@ struct ipv6_fl_socklist {struct rcu_head rcu; }; +static inline struct ipv6_txoptions *txopt_get (const struct ipv6_pinfo *np) +{ +struct ipv6_txoptions *opt; + +rcu_read_lock (); + opt =rcu_dereference (np->opt); +if (opt && !atomic_inc_not_zero (&opt->refcnt)) + opt =NULL ; +rcu_read_unlock (); +return opt; +} + +static inline void txopt_put (struct ipv6_txoptions *opt) +{ +if (opt &&atomic_dec_and_test (&opt->refcnt)) +kfree_rcu (opt, rcu); +} +struct ip6_flowlabel *fl6_sock_lookup (struct sock *sk,__be32 label); struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions *opt_space, struct ip6_flowlabel *fl,
net/dccp/ipv6.c +21 -12
@@ -202,7 +202,9 @@ static int dccp_v6_send_response(structsecurity_req_classify_flow (req,flowi6_to_flowi (&fl6)); - final_p =fl6_update_dst (&fl6, np->opt, &final); +rcu_read_lock (); + final_p =fl6_update_dst (&fl6,rcu_dereference (np->opt), &final); +rcu_read_unlock (); dst =ip6_dst_lookup_flow (sk, &fl6, final_p);if (IS_ERR (dst)) {
@@ -219,7 +221,10 @@ static int dccp_v6_send_response(struct &ireq->ir_v6_loc_addr, &ireq->ir_v6_rmt_addr); fl6.daddr = ireq->ir_v6_rmt_addr; - err =ip6_xmit (sk, skb, &fl6, np->opt, np->tclass); +rcu_read_lock (); + err =ip6_xmit (sk, skb, &fl6,rcu_dereference (np->opt), + np->tclass); +rcu_read_unlock (); err =net_xmit_eval (err); }
@@ -415,6 +420,7 @@ static struct sock *dccp_v6_request_recv {struct inet_request_sock *ireq =inet_rsk (req);struct ipv6_pinfo *newnp, *np =inet6_sk (sk); +struct ipv6_txoptions *opt;struct inet_sock *newinet;struct dccp6_sock *newdp6;struct sock *newsk;
@@ -534,13 +540,15 @@ static struct sock *dccp_v6_request_recv* Yes, keeping reference count would be much more clever, but we make * one more one thing there: reattach optmem to newsk. */ -if (np->opt !=NULL ) - 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 !=NULL ) -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;dccp_sync_mss (newsk,dst_mtu (dst));
@@ -793,6 +801,7 @@ static int dccp_v6_connect(struct sock *struct ipv6_pinfo *np =inet6_sk (sk);struct dccp_sock *dp =dccp_sk (sk);struct in6_addr *saddr =NULL , *final_p, final; +struct ipv6_txoptions *opt;struct flowi6 fl6;struct dst_entry *dst;int addr_type;
@@ -892,7 +901,8 @@ static int dccp_v6_connect(struct sock * fl6.fl6_sport = inet->inet_sport;security_sk_classify_flow (sk,flowi6_to_flowi (&fl6)); - 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); dst =ip6_dst_lookup_flow (sk, &fl6, final_p);if (IS_ERR (dst)) {
@@ -912,9 +922,8 @@ static int dccp_v6_connect(struct sock *__ip6_dst_store (sk, dst,NULL ,NULL ); icsk->icsk_ext_hdr_len =0 ; -if (np->opt !=NULL ) - 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; inet->inet_dport = usin->sin6_port;
net/ipv6/af_inet6.c +9 -4
@@ -426,9 +426,11 @@ void inet6_destroy_sock(struct sock *sk)/* Free tx options */ - opt =xchg (&np->opt,NULL ); -if (opt) -sock_kfree_s (sk, opt, opt->tot_len); + opt =xchg ((__force struct ipv6_txoptions **)&np->opt,NULL ); +if (opt) { +atomic_sub (opt->tot_len, &sk->sk_omem_alloc); +txopt_put (opt); + } }EXPORT_SYMBOL_GPL (inet6_destroy_sock);
@@ -657,7 +659,10 @@ int inet6_sk_rebuild_header(struct sock fl6.fl6_sport = inet->inet_sport;security_sk_classify_flow (sk,flowi6_to_flowi (&fl6)); - final_p =fl6_update_dst (&fl6, np->opt, &final); +rcu_read_lock (); + final_p =fl6_update_dst (&fl6,rcu_dereference (np->opt), + &final); +rcu_read_unlock (); dst =ip6_dst_lookup_flow (sk, &fl6, final_p);if (IS_ERR (dst)) {
net/ipv6/datagram.c +3 -1
@@ -167,8 +167,10 @@ ipv4_connected:security_sk_classify_flow (sk,flowi6_to_flowi (&fl6)); - opt = flowlabel ? flowlabel->opt : np->opt; +rcu_read_lock (); + opt = flowlabel ? flowlabel->opt :rcu_dereference (np->opt); final_p =fl6_update_dst (&fl6, opt, &final); +rcu_read_unlock (); dst =ip6_dst_lookup_flow (sk, &fl6, final_p); err =0 ;
net/ipv6/exthdrs.c +2 -1
@@ -727,6 +727,7 @@ ipv6_dup_options(struct sock *sk, struct *((char **)&opt2->dst1opt) += dif;if (opt2->srcrt) *((char **)&opt2->srcrt) += dif; +atomic_set (&opt2->refcnt,1 ); }return opt2; }
@@ -790,7 +791,7 @@ ipv6_renew_options(struct sock *sk, strureturn ERR_PTR (-ENOBUFS );memset (opt2,0 , tot_len); - +atomic_set (&opt2->refcnt,1 ); opt2->tot_len = tot_len; p = (char *)(opt2 +1 );
net/ipv6/inet6_connection_sock.c +8 -3
@@ -77,7 +77,9 @@ struct dst_entry *inet6_csk_route_req(stmemset (fl6,0 ,sizeof (*fl6)); fl6->flowi6_proto =IPPROTO_TCP ; fl6->daddr = ireq->ir_v6_rmt_addr; - final_p =fl6_update_dst (fl6, np->opt, &final); +rcu_read_lock (); + final_p =fl6_update_dst (fl6,rcu_dereference (np->opt), &final); +rcu_read_unlock (); fl6->saddr = ireq->ir_v6_loc_addr; fl6->flowi6_oif = ireq->ir_iif; fl6->flowi6_mark = ireq->ir_mark;
@@ -207,7 +209,9 @@ static struct dst_entry *inet6_csk_route fl6->fl6_dport = inet->inet_dport;security_sk_classify_flow (sk,flowi6_to_flowi (fl6)); - final_p =fl6_update_dst (fl6, np->opt, &final); +rcu_read_lock (); + final_p =fl6_update_dst (fl6,rcu_dereference (np->opt), &final); +rcu_read_unlock (); dst =__inet6_csk_dst_check (sk, np->dst_cookie);if (!dst) {
@@ -240,7 +244,8 @@ int inet6_csk_xmit(struct sock *sk, stru/* Restore final destination back after routing done */ fl6.daddr = sk->sk_v6_daddr; - res =ip6_xmit (sk, skb, &fl6, np->opt, np->tclass); + res =ip6_xmit (sk, skb, &fl6,rcu_dereference (np->opt), + np->tclass);rcu_read_unlock ();return res; }
net/ipv6/ipv6_sockglue.c +22 -11
@@ -111,7 +111,8 @@ struct ipv6_txoptions *ipv6_update_optio icsk->icsk_sync_mss (sk, icsk->icsk_pmtu_cookie); } } - opt =xchg (&inet6_sk (sk)->opt, opt); + opt =xchg ((__force struct ipv6_txoptions **)&inet6_sk (sk)->opt, + opt);sk_dst_reset (sk);return opt;
@@ -231,9 +232,12 @@ static int do_ipv6_setsockopt(struct soc sk->sk_socket->ops = &inet_dgram_ops; sk->sk_family =PF_INET ; } - opt =xchg (&np->opt,NULL ); -if (opt) -sock_kfree_s (sk, opt, opt->tot_len); + opt =xchg ((__force struct ipv6_txoptions **)&np->opt, +NULL ); +if (opt) { +atomic_sub (opt->tot_len, &sk->sk_omem_alloc); +txopt_put (opt); + } pktopt =xchg (&np->pktoptions,NULL );kfree_skb (pktopt);
@@ -403,7 +407,8 @@ static int do_ipv6_setsockopt(struct socif (optname !=IPV6_RTHDR && !ns_capable (net->user_ns,CAP_NET_RAW ))break ; - opt =ipv6_renew_options (sk, np->opt, optname, + opt =rcu_dereference_protected (np->opt,sock_owned_by_user (sk)); + opt =ipv6_renew_options (sk, opt, optname, (struct ipv6_opt_hdr __user *)optval, optlen);if (IS_ERR (opt)) {
@@ -432,8 +437,10 @@ static int do_ipv6_setsockopt(struct soc retv =0 ; opt =ipv6_update_options (sk, opt); sticky_done: -if (opt) -sock_kfree_s (sk, opt, opt->tot_len); +if (opt) { +atomic_sub (opt->tot_len, &sk->sk_omem_alloc); +txopt_put (opt); + }break ; }
@@ -486,6 +493,7 @@ sticky_done:break ;memset (opt,0 ,sizeof (*opt)); +atomic_set (&opt->refcnt,1 ); opt->tot_len =sizeof (*opt) + optlen; retv = -EFAULT ;if (copy_from_user (opt+1 , optval, optlen))
@@ -502,8 +510,10 @@ update: retv =0 ; opt =ipv6_update_options (sk, opt); done: -if (opt) -sock_kfree_s (sk, opt, opt->tot_len); +if (opt) { +atomic_sub (opt->tot_len, &sk->sk_omem_alloc); +txopt_put (opt); + }break ; }case IPV6_UNICAST_HOPS :
@@ -1110,10 +1120,11 @@ static int do_ipv6_getsockopt(struct soccase IPV6_RTHDR :case IPV6_DSTOPTS : { +struct ipv6_txoptions *opt;lock_sock (sk); - len =ipv6_getsockopt_sticky (sk, np->opt, - optname, optval, len); + opt =rcu_dereference_protected (np->opt,sock_owned_by_user (sk)); + len =ipv6_getsockopt_sticky (sk, opt, optname, optval, len);release_sock (sk);/* check if ipv6_getsockopt_sticky() returns err code */ if (len <0 )
net/ipv6/raw.c +6 -2
@@ -731,6 +731,7 @@ static int raw6_getfrag(void *from, charstatic int rawv6_sendmsg (struct sock *sk,struct msghdr *msg,size_t len) { +struct ipv6_txoptions *opt_to_free =NULL ;struct ipv6_txoptions opt_space; DECLARE_SOCKADDR(struct sockaddr_in6 *, sin6, msg->msg_name);struct in6_addr *daddr, *final_p, final;
@@ -837,8 +838,10 @@ static int rawv6_sendmsg(struct sock *skif (!(opt->opt_nflen|opt->opt_flen)) opt =NULL ; } -if (!opt) - opt = np->opt; +if (!opt) { + opt =txopt_get (np); + opt_to_free = opt; + }if (flowlabel) opt =fl6_merge_options (&opt_space, flowlabel, opt); opt =ipv6_fixup_options (&opt_space, opt);
@@ -904,6 +907,7 @@ done:dst_release (dst); out:fl6_sock_release (flowlabel); +txopt_put (opt_to_free);return err <0 ? err : len; do_confirm:dst_confirm (dst);
net/ipv6/syncookies.c +1 -1
@@ -225,7 +225,7 @@ struct sock *cookie_v6_check(struct sockmemset (&fl6,0 ,sizeof (fl6)); fl6.flowi6_proto =IPPROTO_TCP ; fl6.daddr = ireq->ir_v6_rmt_addr; - final_p =fl6_update_dst (&fl6, np->opt, &final); + final_p =fl6_update_dst (&fl6,rcu_dereference (np->opt), &final); fl6.saddr = ireq->ir_v6_loc_addr; fl6.flowi6_oif = sk->sk_bound_dev_if; fl6.flowi6_mark = ireq->ir_mark;
net/ipv6/tcp_ipv6.c +17 -11
@@ -120,6 +120,7 @@ static int tcp_v6_connect(struct sock *sstruct 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 *s 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 *stcp_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(struct soc fl6->flowlabel =ip6_flowlabel (ipv6_hdr (ireq->pktopts));skb_set_queue_mapping (skb, queue_mapping); - 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); }
@@ -991,6 +994,7 @@ static struct sock *tcp_v6_syn_recv_sockstruct inet_request_sock *ireq;struct ipv6_pinfo *newnp, *np =inet6_sk (sk);struct tcp6_sock *newtcp6sk; +struct ipv6_txoptions *opt;struct inet_sock *newinet;struct tcp_sock *newtp;struct sock *newsk;
@@ -1126,13 +1130,15 @@ static struct sock *tcp_v6_syn_recv_sockbut 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);
net/ipv6/udp.c +6 -2
@@ -1107,6 +1107,7 @@ int udpv6_sendmsg(struct sock *sk, struc DECLARE_SOCKADDR(struct sockaddr_in6 *, sin6, msg->msg_name);struct in6_addr *daddr, *final_p, final;struct ipv6_txoptions *opt =NULL ; +struct ipv6_txoptions *opt_to_free =NULL ;struct ip6_flowlabel *flowlabel =NULL ;struct flowi6 fl6;struct dst_entry *dst;
@@ -1260,8 +1261,10 @@ do_udp_sendmsg: opt =NULL ; connected =0 ; } -if (!opt) - opt = np->opt; +if (!opt) { + opt =txopt_get (np); + opt_to_free = opt; + }if (flowlabel) opt =fl6_merge_options (&opt_space, flowlabel, opt); opt =ipv6_fixup_options (&opt_space, opt);
@@ -1370,6 +1373,7 @@ release_dst: out:dst_release (dst);fl6_sock_release (flowlabel); +txopt_put (opt_to_free);if (!err)return len;/*
net/l2tp/l2tp_ip6.c +6 -2
@@ -486,6 +486,7 @@ static int l2tp_ip6_sendmsg(struct sock DECLARE_SOCKADDR(struct sockaddr_l2tpip6 *, lsa, msg->msg_name);struct in6_addr *daddr, *final_p, final;struct ipv6_pinfo *np =inet6_sk (sk); +struct ipv6_txoptions *opt_to_free =NULL ;struct ipv6_txoptions *opt =NULL ;struct ip6_flowlabel *flowlabel =NULL ;struct dst_entry *dst =NULL ;
@@ -575,8 +576,10 @@ static int l2tp_ip6_sendmsg(struct sock opt =NULL ; } -if (opt ==NULL ) - opt = np->opt; +if (!opt) { + opt =txopt_get (np); + opt_to_free = opt; + }if (flowlabel) opt =fl6_merge_options (&opt_space, flowlabel, opt); opt =ipv6_fixup_options (&opt_space, opt);
@@ -631,6 +634,7 @@ done:dst_release (dst); out:fl6_sock_release (flowlabel); +txopt_put (opt_to_free);return err <0 ? err : len;
Patches currently in stable-queue which might be from [email protected] are
queue-4.2/ipv6-add-complete-rcu-protection-around-np-opt.patch queue-4.2/packet-infer-protocol-from-ethernet-header-if-unset.patch queue-4.2/af-unix-passcred-support-for-sendpage.patch queue-4.2/ipv6-sctp-implement-sctp_v6_destroy_sock.patch queue-4.2/af_unix-don-t-append-consumed-skbs-to-sk_receive_queue.patch queue-4.2/tcp-disable-fast-open-on-timeouts-after-handshake.patch queue-4.2/net-scm-fix-pax-detected-msg_controllen-overflow-in-scm_detach_fds.patch queue-4.2/tcp-md5-fix-lockdep-annotation.patch queue-4.2/af-unix-fix-use-after-free-with-concurrent-readers-while-splicing.patch queue-4.2/net_sched-fix-qdisc_tree_decrease_qlen-races.patch queue-4.2/tcp-initialize-tp-copied_seq-in-case-of-cross-syn-connection.patch queue-4.2/tcp-fix-potential-huge-kmalloc-calls-in-tcp_repair.patch queue-4.2/packet-do-skb_probe_transport_header-when-we-actually-have-data.patch queue-4.2/af_unix-take-receive-queue-lock-while-appending-new-skb.patch