Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 45f6fad8 authored by Eric Dumazet's avatar Eric Dumazet Committed by David S. Miller
Browse files

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: default avatarDmitry Vyukov <dvyukov@google.com>
Signed-off-by: default avatarEric Dumazet <edumazet@google.com>
Acked-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 01b3f521
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -227,7 +227,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;
+20 −1
Original line number Diff line number Diff line
@@ -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,
+21 −12
Original line number Diff line number Diff line
@@ -202,7 +202,9 @@ static int dccp_v6_send_response(const struct sock *sk, struct request_sock *req
	security_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(const struct sock *sk, struct request_sock *req
							 &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);
	}

@@ -387,6 +392,7 @@ static struct sock *dccp_v6_request_recv_sock(const struct sock *sk,
	struct inet_request_sock *ireq = inet_rsk(req);
	struct ipv6_pinfo *newnp;
	const struct ipv6_pinfo *np = inet6_sk(sk);
	struct ipv6_txoptions *opt;
	struct inet_sock *newinet;
	struct dccp6_sock *newdp6;
	struct sock *newsk;
@@ -488,13 +494,15 @@ static struct sock *dccp_v6_request_recv_sock(const struct sock *sk,
	 * 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));

@@ -757,6 +765,7 @@ static int dccp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
	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;
@@ -856,7 +865,8 @@ static int dccp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
	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)) {
@@ -876,9 +886,8 @@ static int dccp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
	__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;

+9 −4
Original line number Diff line number Diff line
@@ -428,9 +428,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);

@@ -659,7 +661,10 @@ int inet6_sk_rebuild_header(struct sock *sk)
		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)) {
+3 −1
Original line number Diff line number Diff line
@@ -167,8 +167,10 @@ static int __ip6_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int a

	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;
Loading