Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 27922b4260f65d317aabda37e42bbbff > files > 1629

kernel-2.6.18-238.el5.src.rpm

From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Sun, 6 Jan 2008 16:09:46 +1100
Subject: [ipsec] add ICMP host relookup support
Message-id: E1JBNla-0001DL-00@gondolin.me.apana.org.au
O-Subject: [PATCH 30/32] [IPSEC]: Add ICMP host relookup support
Bugzilla: 427876

[IPSEC]: Add ICMP host relookup support

RFC 4301 requires us to relookup ICMP traffic that does not match any
policies using the reverse of its payload.  This patch implements this
for ICMP traffic that originates from or terminates on localhost.

This is activated on outbound with the new policy flag XFRM_POLICY_ICMP,
and on inbound by the new state flag XFRM_STATE_ICMP.

On inbound the policy check is now performed by the ICMP protocol so
that it can repeat the policy check where necessary.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Acked-by: "David S. Miller" <davem@redhat.com>

diff --git a/include/linux/xfrm.h b/include/linux/xfrm.h
index 388176b..b875989 100644
--- a/include/linux/xfrm.h
+++ b/include/linux/xfrm.h
@@ -262,6 +262,7 @@ struct xfrm_usersa_info {
 #define XFRM_STATE_NOECN	1
 #define XFRM_STATE_DECAP_DSCP	2
 #define XFRM_STATE_NOPMTUDISC	4
+#define XFRM_STATE_ICMP		16
 };
 
 struct xfrm_usersa_id {
@@ -294,6 +295,8 @@ struct xfrm_userpolicy_info {
 #define XFRM_POLICY_BLOCK	1
 	__u8				flags;
 #define XFRM_POLICY_LOCALOK	1	/* Allow user to override global policy */
+	/* Automatically expand selector to include matching ICMP payloads. */
+#define XFRM_POLICY_ICMP	2
 	__u8				share;
 };
 
diff --git a/include/net/dst.h b/include/net/dst.h
index 865f823..4aa2f02 100644
--- a/include/net/dst.h
+++ b/include/net/dst.h
@@ -255,6 +255,7 @@ extern void		dst_init(void);
 /* Flags for xfrm_lookup flags argument. */
 enum {
 	XFRM_LOOKUP_WAIT = 1 << 0,
+	XFRM_LOOKUP_ICMP = 1 << 1,
 };
 
 struct flowi;
@@ -264,6 +265,11 @@ static inline int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
 {
 	return 0;
 } 
+static inline int xfrm_nlookup(struct dst_entry **dst_p, struct flowi *fl,
+			       struct sock *sk, int flags)
+{
+	return 0;
+} 
 static inline int __xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
 				struct sock *sk, int flags)
 {
@@ -272,6 +278,8 @@ static inline int __xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
 #else
 extern int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
 		       struct sock *sk, int flags);
+extern int xfrm_nlookup(struct dst_entry **dst_p, struct flowi *fl,
+			struct sock *sk, int flags);
 extern int __xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
 			 struct sock *sk, int flags);
 #endif
diff --git a/include/net/icmp.h b/include/net/icmp.h
index 64ac00b..098161d 100644
--- a/include/net/icmp.h
+++ b/include/net/icmp.h
@@ -72,4 +72,7 @@ extern int sysctl_icmp_errors_use_inbound_ifaddr;
 extern int sysctl_icmp_ratelimit;
 extern int sysctl_icmp_ratemask;
 
+extern void xfrm4_decode_session_reverse(struct sk_buff *skb, struct flowi *fl);
+extern int xfrm4_icmp_check(struct sk_buff *skb);
+
 #endif	/* _ICMP_H */
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index a0cd985..57e4dcb 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1209,6 +1209,7 @@ static struct net_protocol udp_protocol = {
 
 static struct net_protocol icmp_protocol = {
 	.handler =	icmp_rcv,
+	.no_policy =	1,
 };
 
 static int __init init_ipv4_mibs(void)
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index b6ae67b..275ebd8 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -92,6 +92,7 @@
 #include <asm/system.h>
 #include <asm/uaccess.h>
 #include <net/checksum.h>
+#include <linux/xfrm.h>
 
 /*
  *	Build xmit assembly blocks
@@ -557,11 +558,70 @@ void icmp_send(struct sk_buff *skb_in, int type, int code, u32 info)
 				}
 			}
 		};
+		int err;
+		struct rtable *rt2;
+
 		security_skb_classify_flow(skb_in, &fl);
-		if (ip_route_output_key(&rt, &fl))
+		if (__ip_route_output_key(&rt, &fl))
+			goto out_unlock;
+
+		/* No need to clone since we're just using its address. */
+		rt2 = rt;
+
+		err = xfrm_lookup((struct dst_entry **)&rt, &fl, NULL, 0);
+		switch (err) {
+		case 0:
+			if (rt != rt2)
+				goto route_done;
+			break;
+		case -EPERM:
+			rt = NULL;
+			break;
+		default:
+			goto out_unlock;
+		}
+
+		xfrm4_decode_session_reverse(skb_in, &fl);
+
+		if (inet_addr_type(fl.fl4_src) == RTN_LOCAL)
+			err = __ip_route_output_key(&rt2, &fl);
+		else {
+			struct flowi fl2 = {};
+			struct dst_entry *odst;
+
+			fl2.fl4_dst = fl.fl4_src;
+			if (ip_route_output_key(&rt2, &fl2))
+				goto out_unlock;
+
+			/* Ugh! */
+			odst = skb_in->dst;
+			err = ip_route_input(skb_in, fl.fl4_dst, fl.fl4_src,
+					     RT_TOS(tos), rt2->u.dst.dev);
+
+			dst_release(&rt2->u.dst);
+			rt2 = (struct rtable *)skb_in->dst;
+			skb_in->dst = odst;
+		}
+
+		if (err)
+			goto out_unlock;
+
+		err = xfrm_lookup((struct dst_entry **)&rt2, &fl, NULL,
+				  XFRM_LOOKUP_ICMP);
+		if (err == -ENOENT) {
+			if (!rt)
+				goto out_unlock;
+			goto route_done;
+		}
+
+		dst_release(&rt->u.dst);
+		rt = rt2;
+
+		if (err)
 			goto out_unlock;
 	}
 
+route_done:
 	if (!icmpv4_xrlim_allow(rt, type, code))
 		goto ende;
 
@@ -923,6 +983,9 @@ int icmp_rcv(struct sk_buff *skb)
 	struct icmphdr *icmph;
 	struct rtable *rt = (struct rtable *)skb->dst;
 
+	if (!xfrm4_icmp_check(skb))
+		goto drop;
+
 	ICMP_INC_STATS_BH(ICMP_MIB_INMSGS);
 
 	switch (skb->ip_summed) {
@@ -936,8 +999,7 @@ int icmp_rcv(struct sk_buff *skb)
 			goto error;
 	}
 
-	if (!pskb_pull(skb, sizeof(struct icmphdr)))
-		goto error;
+	__skb_pull(skb, sizeof(*icmph));
 
 	icmph = skb->h.icmph;
 
diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c
index 24bbfcb..59ffe0f 100644
--- a/net/ipv4/xfrm4_policy.c
+++ b/net/ipv4/xfrm4_policy.c
@@ -10,6 +10,7 @@
 
 #include <linux/compiler.h>
 #include <linux/inetdevice.h>
+#include <net/icmp.h>
 #include <net/xfrm.h>
 #include <net/ip.h>
 
@@ -311,6 +312,29 @@ static struct xfrm_policy_afinfo xfrm4_policy_afinfo = {
 	.decode_session =	_decode_session4,
 };
 
+int xfrm4_icmp_check(struct sk_buff *skb)
+{
+	struct icmphdr *icmph;
+	int ok;
+
+	ok = xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb);
+
+	if (!ok) {
+		if (!(skb->sp && skb->sp->xvec[skb->sp->len - 1]->props.flags &
+				 XFRM_STATE_ICMP))
+			return 0;
+
+		if (!pskb_may_pull(skb, sizeof(*icmph) + sizeof(struct iphdr)))
+			return 0;
+
+		skb->nh.raw += sizeof(*icmph);
+		ok = xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN, skb);
+		skb->nh.raw -= sizeof(*icmph);
+	}
+
+	return ok;
+}
+
 static void __init xfrm4_policy_init(void)
 {
 	xfrm_policy_register_afinfo(&xfrm4_policy_afinfo);
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index 36fe967..bedae8f 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -5,6 +5,7 @@
 #   IPv6 as module will cause a CRASH if you try to unload it
 config IPV6
 	tristate "The IPv6 protocol"
+	select XFRM_NALGO
 	default m
 	---help---
 	  This is complemental support for the IP version 6.
diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index 983873b..4d5bc4c 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -64,6 +64,7 @@
 #include <net/ip6_route.h>
 #include <net/addrconf.h>
 #include <net/icmp.h>
+#include <net/xfrm.h>
 
 #include <asm/uaccess.h>
 #include <asm/system.h>
@@ -85,7 +86,7 @@ static int icmpv6_rcv(struct sk_buff **pskb);
 
 static struct inet6_protocol icmpv6_protocol = {
 	.handler	=	icmpv6_rcv,
-	.flags		=	INET6_PROTO_FINAL,
+	.flags		=	INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
 };
 
 static __inline__ int icmpv6_xmit_lock(void)
@@ -287,8 +288,10 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
 	struct ipv6_pinfo *np;
 	struct in6_addr *saddr = NULL;
 	struct dst_entry *dst;
+	struct dst_entry *dst2;
 	struct icmp6hdr tmp_hdr;
 	struct flowi fl;
+	struct flowi fl2;
 	struct icmpv6_msg msg;
 	int iif = 0;
 	int addr_type = 0;
@@ -392,9 +395,42 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
 		goto out_dst_release;
 	}
 
-	if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
+	/* No need to clone since we're just using its address. */
+	dst2 = dst;
+
+	err = xfrm_lookup(&dst, &fl, sk, 0);
+	switch (err) {
+	case 0:
+		if (dst != dst2)
+			goto route_done;
+		break;
+	case -EPERM:
+		dst = NULL;
+		break;
+	default:
+		goto out;
+	}
+
+	xfrm6_decode_session_reverse(skb, &fl2);
+
+	if (ip6_dst_lookup(sk, &dst2, &fl))
+		goto out;
+
+	err = xfrm_nlookup(&dst2, &fl, sk, XFRM_LOOKUP_ICMP);
+	if (err == -ENOENT || err == -ENOSYS) {
+		err = -ENOENT;
+		if (!dst)
+			goto out;
+		goto route_done;
+	}
+
+	dst_release(dst);
+	dst = dst2;
+
+	if (err)
 		goto out;
 
+route_done:
 	if (ipv6_addr_is_multicast(&fl.fl6_dst))
 		hlimit = np->mcast_hops;
 	else
@@ -596,6 +632,22 @@ static int icmpv6_rcv(struct sk_buff **pskb)
 	struct icmp6hdr *hdr;
 	int type;
 
+	if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
+		if (!(skb->sp && skb->sp->xvec[skb->sp->len - 1]->props.flags &
+				 XFRM_STATE_ICMP))
+			goto drop_no_count;
+
+		if (!pskb_may_pull(skb, sizeof(*hdr) + sizeof(*orig_hdr)))
+			goto drop_no_count;
+
+		skb->nh.raw += sizeof(*hdr);
+
+		if (!xfrm6_policy_check_reverse(NULL, XFRM_POLICY_IN, skb))
+			goto drop_no_count;
+
+		skb->nh.raw -= sizeof(*hdr);
+	}
+
 	ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INMSGS);
 
 	saddr = &skb->nh.ipv6h->saddr;
@@ -618,8 +670,7 @@ static int icmpv6_rcv(struct sk_buff **pskb)
 		}
 	}
 
-	if (!pskb_pull(skb, sizeof(struct icmp6hdr)))
-		goto discard_it;
+	__skb_pull(skb, sizeof(*hdr));
 
 	hdr = (struct icmp6hdr *) skb->h.raw;
 
@@ -704,6 +755,7 @@ static int icmpv6_rcv(struct sk_buff **pskb)
 
 discard_it:
 	ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INERRORS);
+drop_no_count:
 	kfree_skb(skb);
 	return 0;
 }
diff --git a/net/xfrm/xfrm_nalgo.c b/net/xfrm/xfrm_nalgo.c
index 1fa7872..116cc65 100644
--- a/net/xfrm/xfrm_nalgo.c
+++ b/net/xfrm/xfrm_nalgo.c
@@ -9,6 +9,7 @@
  * any later version.
  */
 
+#include <linux/init.h>
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/pfkeyv2.h>
@@ -16,6 +17,8 @@
 #include <linux/scatterlist.h>
 #include <net/xfrm.h>
 
+static int xfrm_old_kernel;
+
 /*
  * Algorithms supported by IPsec.  These entries contain properties which
  * are used in key negotiation and xfrm processing, and are used to verify
@@ -489,4 +492,26 @@ struct xfrm_nalgo_desc *xfrm_naead_get_byname(char *name, int icv_len,
 }
 EXPORT_SYMBOL_GPL(xfrm_naead_get_byname);
 
+int xfrm_nlookup(struct dst_entry **dst_p, struct flowi *fl,
+		 struct sock *sk, int flags)
+{
+	if (xfrm_old_kernel && (flags & ~XFRM_LOOKUP_WAIT))
+		return -ENOSYS;
+
+	return xfrm_lookup(dst_p, fl, sk, flags);
+}
+EXPORT_SYMBOL(xfrm_nlookup);
+
+static int __init xfrm_nalgo_init(void)
+{
+	xfrm_old_kernel = !xfrm_aalg_get_byname("hmac(md5)", 1);
+	return 0;
+}
+
+static void __exit xfrm_nalgo_exit(void)
+{
+}
+
+module_init(xfrm_nalgo_init);
+module_exit(xfrm_nalgo_exit);
 MODULE_LICENSE("GPL");
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 65522db..dc9cb35 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -946,7 +946,7 @@ restart:
 	if (!policy) {
 		/* To accelerate a bit...  */
 		if ((dst_orig->flags & DST_NOXFRM) || !xfrm_policy_list[XFRM_POLICY_OUT])
-			return 0;
+			goto nopol;
 
 		policy = flow_cache_lookup(fl, dst_orig->ops->family,
 					   dir, xfrm_policy_lookup);
@@ -955,7 +955,11 @@ restart:
 	}
 
 	if (!policy)
-		return 0;
+		goto nopol;
+
+	err = -ENOENT;
+	if ((flags & XFRM_LOOKUP_ICMP) && !(policy->flags & XFRM_POLICY_ICMP))
+		goto error;
 
 	family = dst_orig->ops->family;
 	policy->curlft.use_time = (unsigned long)xtime.tv_sec;
@@ -1064,10 +1068,17 @@ restart:
 	return 0;
 
 error:
-	dst_release(dst_orig);
 	xfrm_pol_put(policy);
+dropdst:
+	dst_release(dst_orig);
 	*dst_p = NULL;
 	return err;
+
+nopol:
+	err = -ENOENT;
+	if (flags & XFRM_LOOKUP_ICMP)
+		goto dropdst;
+	return 0;
 }
 EXPORT_SYMBOL(__xfrm_lookup);