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

Commit fda9ef5d authored by Dmitry Mishin's avatar Dmitry Mishin Committed by David S. Miller
Browse files

[NET]: Fix sk->sk_filter field access



Function sk_filter() is called from tcp_v{4,6}_rcv() functions with arg
needlock = 0, while socket is not locked at that moment. In order to avoid
this and similar issues in the future, use rcu for sk->sk_filter field read
protection.

Signed-off-by: default avatarDmitry Mishin <dim@openvz.org>
Signed-off-by: default avatarAlexey Kuznetsov <kuznet@ms2.inr.ac.ru>
Signed-off-by: default avatarKirill Korotaev <dev@openvz.org>
parent dc435e6d
Loading
Loading
Loading
Loading
+7 −6
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ struct sk_filter
{
	atomic_t		refcnt;
	unsigned int         	len;	/* Number of filter blocks */
	struct rcu_head		rcu;
	struct sock_filter     	insns[0];
};

+17 −17
Original line number Diff line number Diff line
@@ -862,30 +862,24 @@ extern void sock_init_data(struct socket *sock, struct sock *sk);
 *
 */

static inline int sk_filter(struct sock *sk, struct sk_buff *skb, int needlock)
static inline int sk_filter(struct sock *sk, struct sk_buff *skb)
{
	int err;
	struct sk_filter *filter;
	
	err = security_sock_rcv_skb(sk, skb);
	if (err)
		return err;
	
	if (sk->sk_filter) {
		struct sk_filter *filter;
		
		if (needlock)
			bh_lock_sock(sk);
		
	rcu_read_lock_bh();
	filter = sk->sk_filter;
	if (filter) {
		unsigned int pkt_len = sk_run_filter(skb, filter->insns,
				filter->len);
		err = pkt_len ? pskb_trim(skb, pkt_len) : -EPERM;
	}
 	rcu_read_unlock_bh();

		if (needlock)
			bh_unlock_sock(sk);
	}
	return err;
}

@@ -897,6 +891,12 @@ static inline int sk_filter(struct sock *sk, struct sk_buff *skb, int needlock)
 *	Remove a filter from a socket and release its resources.
 */
 
static inline void sk_filter_rcu_free(struct rcu_head *rcu)
{
	struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu);
	kfree(fp);
}

static inline void sk_filter_release(struct sock *sk, struct sk_filter *fp)
{
	unsigned int size = sk_filter_len(fp);
@@ -904,7 +904,7 @@ static inline void sk_filter_release(struct sock *sk, struct sk_filter *fp)
	atomic_sub(size, &sk->sk_omem_alloc);

	if (atomic_dec_and_test(&fp->refcnt))
		kfree(fp);
		call_rcu_bh(&fp->rcu, sk_filter_rcu_free);
}

static inline void sk_filter_charge(struct sock *sk, struct sk_filter *fp)
+4 −4
Original line number Diff line number Diff line
@@ -422,10 +422,10 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
	if (!err) {
		struct sk_filter *old_fp;

		spin_lock_bh(&sk->sk_lock.slock);
		old_fp = sk->sk_filter;
		sk->sk_filter = fp;
		spin_unlock_bh(&sk->sk_lock.slock);
		rcu_read_lock_bh();
		old_fp = rcu_dereference(sk->sk_filter);
		rcu_assign_pointer(sk->sk_filter, fp);
		rcu_read_unlock_bh();
		fp = old_fp;
	}

+9 −13
Original line number Diff line number Diff line
@@ -247,11 +247,7 @@ int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
		goto out;
	}

	/* It would be deadlock, if sock_queue_rcv_skb is used
	   with socket lock! We assume that users of this
	   function are lock free.
	*/
	err = sk_filter(sk, skb, 1);
	err = sk_filter(sk, skb);
	if (err)
		goto out;

@@ -278,7 +274,7 @@ int sk_receive_skb(struct sock *sk, struct sk_buff *skb)
{
	int rc = NET_RX_SUCCESS;

	if (sk_filter(sk, skb, 0))
	if (sk_filter(sk, skb))
		goto discard_and_relse;

	skb->dev = NULL;
@@ -606,15 +602,15 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
			break;

		case SO_DETACH_FILTER:
			spin_lock_bh(&sk->sk_lock.slock);
			filter = sk->sk_filter;
			rcu_read_lock_bh();
			filter = rcu_dereference(sk->sk_filter);
                        if (filter) {
				sk->sk_filter = NULL;
				spin_unlock_bh(&sk->sk_lock.slock);
				rcu_assign_pointer(sk->sk_filter, NULL);
				sk_filter_release(sk, filter);
				rcu_read_unlock_bh();
				break;
			}
			spin_unlock_bh(&sk->sk_lock.slock);
			rcu_read_unlock_bh();
			ret = -ENONET;
			break;

@@ -884,10 +880,10 @@ void sk_free(struct sock *sk)
	if (sk->sk_destruct)
		sk->sk_destruct(sk);

	filter = sk->sk_filter;
	filter = rcu_dereference(sk->sk_filter);
	if (filter) {
		sk_filter_release(sk, filter);
		sk->sk_filter = NULL;
		rcu_assign_pointer(sk->sk_filter, NULL);
	}

	sock_disable_timestamp(sk);
+1 −1
Original line number Diff line number Diff line
@@ -970,7 +970,7 @@ static int dccp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
	if (skb->protocol == htons(ETH_P_IP))
		return dccp_v4_do_rcv(sk, skb);

	if (sk_filter(sk, skb, 0))
	if (sk_filter(sk, skb))
		goto discard;

	/*
Loading