diff options
Diffstat (limited to 'net/xfrm/xfrm_input.c')
| -rw-r--r-- | net/xfrm/xfrm_input.c | 128 | 
1 files changed, 114 insertions, 14 deletions
diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c index 45f1c98d4fc..85d1d476461 100644 --- a/net/xfrm/xfrm_input.c +++ b/net/xfrm/xfrm_input.c @@ -16,6 +16,81 @@  static struct kmem_cache *secpath_cachep __read_mostly; +static DEFINE_SPINLOCK(xfrm_input_afinfo_lock); +static struct xfrm_input_afinfo __rcu *xfrm_input_afinfo[NPROTO]; + +int xfrm_input_register_afinfo(struct xfrm_input_afinfo *afinfo) +{ +	int err = 0; + +	if (unlikely(afinfo == NULL)) +		return -EINVAL; +	if (unlikely(afinfo->family >= NPROTO)) +		return -EAFNOSUPPORT; +	spin_lock_bh(&xfrm_input_afinfo_lock); +	if (unlikely(xfrm_input_afinfo[afinfo->family] != NULL)) +		err = -ENOBUFS; +	else +		rcu_assign_pointer(xfrm_input_afinfo[afinfo->family], afinfo); +	spin_unlock_bh(&xfrm_input_afinfo_lock); +	return err; +} +EXPORT_SYMBOL(xfrm_input_register_afinfo); + +int xfrm_input_unregister_afinfo(struct xfrm_input_afinfo *afinfo) +{ +	int err = 0; + +	if (unlikely(afinfo == NULL)) +		return -EINVAL; +	if (unlikely(afinfo->family >= NPROTO)) +		return -EAFNOSUPPORT; +	spin_lock_bh(&xfrm_input_afinfo_lock); +	if (likely(xfrm_input_afinfo[afinfo->family] != NULL)) { +		if (unlikely(xfrm_input_afinfo[afinfo->family] != afinfo)) +			err = -EINVAL; +		else +			RCU_INIT_POINTER(xfrm_input_afinfo[afinfo->family], NULL); +	} +	spin_unlock_bh(&xfrm_input_afinfo_lock); +	synchronize_rcu(); +	return err; +} +EXPORT_SYMBOL(xfrm_input_unregister_afinfo); + +static struct xfrm_input_afinfo *xfrm_input_get_afinfo(unsigned int family) +{ +	struct xfrm_input_afinfo *afinfo; + +	if (unlikely(family >= NPROTO)) +		return NULL; +	rcu_read_lock(); +	afinfo = rcu_dereference(xfrm_input_afinfo[family]); +	if (unlikely(!afinfo)) +		rcu_read_unlock(); +	return afinfo; +} + +static void xfrm_input_put_afinfo(struct xfrm_input_afinfo *afinfo) +{ +	rcu_read_unlock(); +} + +static int xfrm_rcv_cb(struct sk_buff *skb, unsigned int family, u8 protocol, +		       int err) +{ +	int ret; +	struct xfrm_input_afinfo *afinfo = xfrm_input_get_afinfo(family); + +	if (!afinfo) +		return -EAFNOSUPPORT; + +	ret = afinfo->callback(skb, protocol, err); +	xfrm_input_put_afinfo(afinfo); + +	return ret; +} +  void __secpath_destroy(struct sec_path *sp)  {  	int i; @@ -67,7 +142,7 @@ int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)  	case IPPROTO_COMP:  		if (!pskb_may_pull(skb, sizeof(struct ip_comp_hdr)))  			return -EINVAL; -		*spi = htonl(ntohs(*(__be16*)(skb_transport_header(skb) + 2))); +		*spi = htonl(ntohs(*(__be16 *)(skb_transport_header(skb) + 2)));  		*seq = 0;  		return 0;  	default: @@ -77,8 +152,8 @@ int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)  	if (!pskb_may_pull(skb, hlen))  		return -EINVAL; -	*spi = *(__be32*)(skb_transport_header(skb) + offset); -	*seq = *(__be32*)(skb_transport_header(skb) + offset_seq); +	*spi = *(__be32 *)(skb_transport_header(skb) + offset); +	*seq = *(__be32 *)(skb_transport_header(skb) + offset_seq);  	return 0;  } @@ -107,7 +182,8 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)  	struct net *net = dev_net(skb->dev);  	int err;  	__be32 seq; -	struct xfrm_state *x; +	__be32 seq_hi; +	struct xfrm_state *x = NULL;  	xfrm_address_t *daddr;  	struct xfrm_mode *inner_mode;  	unsigned int family; @@ -118,10 +194,15 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)  	if (encap_type < 0) {  		async = 1;  		x = xfrm_input_state(skb); -		seq = XFRM_SKB_CB(skb)->seq.input; +		seq = XFRM_SKB_CB(skb)->seq.input.low; +		family = x->outer_mode->afinfo->family;  		goto resume;  	} +	daddr = (xfrm_address_t *)(skb_network_header(skb) + +				   XFRM_SPI_SKB_CB(skb)->daddroff); +	family = XFRM_SPI_SKB_CB(skb)->family; +  	/* Allocate new secpath or COW existing one. */  	if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {  		struct sec_path *sp; @@ -136,10 +217,6 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)  		skb->sp = sp;  	} -	daddr = (xfrm_address_t *)(skb_network_header(skb) + -				   XFRM_SPI_SKB_CB(skb)->daddroff); -	family = XFRM_SPI_SKB_CB(skb)->family; -  	seq = 0;  	if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) {  		XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR); @@ -161,7 +238,17 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)  		skb->sp->xvec[skb->sp->len++] = x; +		if (xfrm_tunnel_check(skb, x, family)) { +			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR); +			goto drop; +		} +  		spin_lock(&x->lock); +		if (unlikely(x->km.state == XFRM_STATE_ACQ)) { +			XFRM_INC_STATS(net, LINUX_MIB_XFRMACQUIREERROR); +			goto drop_unlock; +		} +  		if (unlikely(x->km.state != XFRM_STATE_VALID)) {  			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEINVALID);  			goto drop_unlock; @@ -172,7 +259,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)  			goto drop_unlock;  		} -		if (x->props.replay_window && xfrm_replay_check(x, skb, seq)) { +		if (x->repl->check(x, skb, seq)) {  			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR);  			goto drop_unlock;  		} @@ -184,13 +271,17 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)  		spin_unlock(&x->lock); -		XFRM_SKB_CB(skb)->seq.input = seq; +		seq_hi = htonl(xfrm_replay_seqhi(x, seq)); + +		XFRM_SKB_CB(skb)->seq.input.low = seq; +		XFRM_SKB_CB(skb)->seq.input.hi = seq_hi; + +		skb_dst_force(skb);  		nexthdr = x->type->input(x, skb);  		if (nexthdr == -EINPROGRESS)  			return 0; -  resume:  		spin_lock(&x->lock);  		if (nexthdr <= 0) { @@ -206,8 +297,12 @@ resume:  		/* only the first xfrm gets the encap type */  		encap_type = 0; -		if (x->props.replay_window) -			xfrm_replay_advance(x, seq); +		if (async && x->repl->recheck(x, skb, seq)) { +			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR); +			goto drop_unlock; +		} + +		x->repl->advance(x, seq);  		x->curlft.bytes += skb->len;  		x->curlft.packets++; @@ -248,6 +343,10 @@ resume:  		}  	} while (!err); +	err = xfrm_rcv_cb(skb, family, x->type->proto, 0); +	if (err) +		goto drop; +  	nf_reset(skb);  	if (decaps) { @@ -261,6 +360,7 @@ resume:  drop_unlock:  	spin_unlock(&x->lock);  drop: +	xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1);  	kfree_skb(skb);  	return 0;  }  | 
